From d72805d1fe61806342bfe0c25da36ca967a57fcc Mon Sep 17 00:00:00 2001 From: tom lee Date: Mon, 3 Jan 2022 00:01:37 +0800 Subject: [PATCH] Buffers: Basically redid the buffer management Note: Backface Culling currently disabled --- .../bufferBuilding/CubicLodTemplate.java | 5 +- .../LodBufferBuilderFactory.java | 1234 ++++++++--------- .../lod/core/objects/lod/LodDimension.java | 111 +- .../core/objects/opengl/LodVertexBuffer.java | 6 +- .../seibel/lod/core/render/LodRenderer.java | 352 ++--- .../seibel/lod/core/render/RenderUtil.java | 15 +- .../com/seibel/lod/core/util/GridList.java | 76 + .../com/seibel/lod/core/util/LodUtil.java | 6 - .../seibel/lod/core/util/MovableGridList.java | 168 +++ 9 files changed, 1042 insertions(+), 931 deletions(-) create mode 100644 src/main/java/com/seibel/lod/core/util/GridList.java create mode 100644 src/main/java/com/seibel/lod/core/util/MovableGridList.java diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/CubicLodTemplate.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/CubicLodTemplate.java index f3240a414..a12229f84 100644 --- a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/CubicLodTemplate.java +++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/CubicLodTemplate.java @@ -131,13 +131,14 @@ public class CubicLodTemplate //if(vertexOptimizer.isCulled(lodDirection)) // continue; // culling - + // FIXME: Reimpl backface culling + /* if (lodDirection == LodDirection.NORTH && vertexOptimizer.getZ(lodDirection, 0) < -cullingRangeZ || lodDirection == LodDirection.EAST && vertexOptimizer.getX(lodDirection, 0) > cullingRangeX || lodDirection == LodDirection.SOUTH && vertexOptimizer.getZ(lodDirection, 0) > cullingRangeZ || lodDirection == LodDirection.WEST && vertexOptimizer.getX(lodDirection, 0) < -cullingRangeX) continue; - + */ int verticalFaceIndex = 0; while (vertexOptimizer.shouldRenderFace(lodDirection, verticalFaceIndex)) diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java index 8a845c118..100ffea61 100644 --- a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java +++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/LodBufferBuilderFactory.java @@ -40,6 +40,7 @@ import com.seibel.lod.core.api.ClientApi; import com.seibel.lod.core.enums.LodDirection; import com.seibel.lod.core.enums.config.GpuUploadMethod; 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.VertexOptimizer; @@ -55,6 +56,7 @@ import com.seibel.lod.core.util.DetailDistanceUtil; import com.seibel.lod.core.util.LevelPosUtil; import com.seibel.lod.core.util.LodThreadFactory; 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.ThreadMapUtil; import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; @@ -76,12 +78,21 @@ public class LodBufferBuilderFactory public void end(String source) { timer = System.nanoTime() - timer; if (timer> 16000000) { //16 ms - ClientApi.LOGGER.debug("NOTE: "+source+" took "+Duration.ofNanos(timer)+"!"); + ClientApi.LOGGER.info("NOTE: "+source+" took "+Duration.ofNanos(timer)+"!"); } } } - + + public static class Pos { + public int x; + public int y; + + public Pos(int xx, int yy) { + x = xx; + y = yy; + } + } private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); @@ -117,26 +128,37 @@ public class LodBufferBuilderFactory * can be directly allocated, so we split the regions into smaller sections.
* This keeps track of those sections. */ - public volatile int[][] numberOfBuffersPerRegion; + // TODO: Check why this is unused + //public volatile int[][] numberOfBuffersPerRegion; /** Stores the vertices when building the VBOs */ - public volatile LodBufferBuilder[][][] buildableBuffers; - - /** The OpenGL IDs of the storage buffers used by the buildableVbos */ - public int[][][] buildableStorageBufferIds; - /** The OpenGL IDs of the storage buffers used by the drawableVbos */ - public int[][][] drawableStorageBufferIds; + // FIXME: Use special warparound type of movable grid list in the future + public volatile MovableGridList buildableBuffers; /** Used when building new VBOs */ - public volatile LodVertexBuffer[][][] buildableVbos; + public volatile MovableGridList buildableVbos; + public volatile int buildableCenterBlockX; + public volatile int buildableCenterBlockY; + public volatile int buildableCenterBlockZ; /** VBOs that are sent over to the LodNodeRenderer */ - public volatile LodVertexBuffer[][][] drawableVbos; + public volatile MovableGridList 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 + */ + public volatile boolean frontBufferRequireReset = false; + public volatile boolean allBuffersRequireReset = false; /** * if this is true the LOD buffers are currently being * regenerated. */ public boolean generatingBuffers = false; + /** * if this is true new LOD buffers have been generated @@ -153,25 +175,13 @@ public class LodBufferBuilderFactory /** this is used to prevent multiple threads creating, destroying, or using the buffers at the same time */ private final ReentrantLock bufferLock = new ReentrantLock(); - private volatile VertexOptimizer[][] vertexOptimizerCache; - private volatile PosToRenderContainer[][] setsToRender; - - /** - * This is the ChunkPosWrapper the player was at the last time the buffers were built. - * IE the center of the buffers last time they were built - */ - private volatile int drawableCenterChunkPosX = 0; - private volatile int drawableCenterChunkPosZ = 0; - private volatile int buildableCenterBlockPosX = 0; - private volatile int buildableCenterBlockPosZ = 0; - - private volatile int minCullingRange = SingletonHandler.get(ILodConfigWrapperSingleton.class).client().graphics().advancedGraphics().getBacksideCullingRange(); - private volatile int lastX = 0; - private volatile int lastZ = 0; - - + private MovableGridList vertexOptimizerCache; + private MovableGridList setsToRender; + private int lastX = 0; + private int lastZ = 0; + public LodBufferBuilderFactory() @@ -187,276 +197,153 @@ public class LodBufferBuilderFactory *
* After the buildable buffers have been generated they must be * swapped with the drawable buffers in the LodRenderer to be drawn. + * @return whether it has started a generation task or is blocked */ - public void generateLodBuffersAsync(LodRenderer renderer, LodDimension lodDim, - int playerX, int playerY, int playerZ, boolean fullRegen) + public boolean updateAndSwapLodBuffersAsync(LodRenderer renderer, LodDimension lodDim, + int playerX, int playerY, int playerZ, boolean partialRegen, boolean flushBuffers) { // only allow one generation process to happen at a time if (generatingBuffers) - return; - - if (buildableBuffers == null) - // setupBuffers hasn't been called yet - return; + return false; if (MC.getCurrentLightMap() == null) // the lighting hasn't loaded yet - return; + return false; + + allBuffersRequireReset |= flushBuffers; + + boolean fullRegen; + if (switchVbos) { + fullRegen = swapBuffers(); + } else { + fullRegen = allBuffersRequireReset || frontBufferRequireReset; + } + if (!fullRegen && !partialRegen) return false; generatingBuffers = true; - - Thread thread = new Thread(() -> generateLodBuffersThread(renderer, lodDim, playerX, playerY, playerZ, fullRegen)); + Runnable thread = () -> generateLodBuffersThread( + renderer, lodDim, playerX, playerY, playerZ, fullRegen); mainGenThread.execute(thread); + return true; } // this was pulled out as a separate method so that it could be // more easily edited by hot swapping. Because, As far as James is aware // you can't hot swap lambda expressions. - private static final boolean enableLogging = false; + private static final boolean enableLogging = true; private void generateLodBuffersThread(LodRenderer renderer, LodDimension lodDim, int playerX, int playerY, int playerZ, boolean fullRegen) { bufferLock.lock(); + GLProxy glProxy = GLProxy.getInstance(); + GLProxyContext oldContext = glProxy.getGlContext(); + glProxy.setGlContext(GLProxyContext.LOD_BUILDER); + + + long startTime = System.currentTimeMillis(); + ArrayList posToUpload = new ArrayList(); + ArrayList> nodeToRenderThreads = new ArrayList>(); try - { + { // round the player's block position down to the nearest chunk BlockPos - int playerChunkX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerX,LodUtil.CHUNK_DETAIL_LEVEL); - int playerChunkZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerZ,LodUtil.CHUNK_DETAIL_LEVEL); - //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 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; - long startTime = System.currentTimeMillis(); - - ArrayList> nodeToRenderThreads = new ArrayList<>(lodDim.getWidth() * lodDim.getWidth()); - - startBuffers(fullRegen, lodDim); - - if (setsToRender == null) - setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()]; - - if (setsToRender.length != lodDim.getWidth()) - setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()]; - - if (vertexOptimizerCache == null) - vertexOptimizerCache = new VertexOptimizer[lodDim.getWidth()][lodDim.getWidth()]; - - if (vertexOptimizerCache.length != lodDim.getWidth()) - vertexOptimizerCache = new VertexOptimizer[lodDim.getWidth()][lodDim.getWidth()]; - - // this will be the center of the VBOs once they have been built - //buildableCenterChunkPosX = playerChunkX; - //buildableCenterChunkPosZ = playerChunkZ; - buildableCenterBlockPosX = playerX; - buildableCenterBlockPosZ = playerZ; - + if (fullRegen || buildableBuffers==null || buildableVbos==null + || setsToRender==null || vertexOptimizerCache==null) { + renderRange = lodDim.getWidth()/2; //get lodDim half width + buildableBuffers = new MovableGridList(renderRange, playerRegionX, playerRegionZ); + buildableVbos = new MovableGridList(renderRange, playerRegionX, playerRegionZ); + setsToRender = new MovableGridList(renderRange, playerRegionX, playerRegionZ); + vertexOptimizerCache = new MovableGridList(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 = buildableBuffers.gridCentreToEdge; + vboX = buildableCenterBlockX; + vboY = buildableCenterBlockY; + vboZ = buildableCenterBlockZ; + buildableBuffers.move(playerRegionX, playerRegionZ); + buildableVbos.move(playerRegionX, playerRegionZ); + setsToRender.move(playerRegionX, playerRegionZ); + vertexOptimizerCache.move(playerRegionX, playerRegionZ); + } + posToUpload.ensureCapacity(buildableVbos.size()); + nodeToRenderThreads.ensureCapacity(buildableVbos.size()); //================================// // create the nodeToRenderThreads // //================================// skyLightPlayer = MC.getWrappedClientWorld().getSkyLight(playerX, playerY, playerZ); - + int minCullingRange = SingletonHandler.get(ILodConfigWrapperSingleton.class).client().graphics().advancedGraphics().getBacksideCullingRange(); int cullingRangeX = Math.max((int)(1.5 * Math.abs(lastX - playerX)), minCullingRange); int cullingRangeZ = Math.max((int)(1.5 * Math.abs(lastZ - playerZ)), minCullingRange); - lastX = playerX; lastZ = playerZ; - for (int xRegion = 0; xRegion < lodDim.getWidth(); xRegion++) + for (int indexX = 0; indexX < buildableVbos.gridSize; indexX++) { - for (int zRegion = 0; zRegion < lodDim.getWidth(); zRegion++) + for (int indexZ = 0; indexZ < buildableVbos.gridSize; indexZ++) { - if (lodDim.doesRegionNeedBufferRegen(xRegion, zRegion) || fullRegen) - { - RegionPos regionPos = new RegionPos( - xRegion + lodDim.getCenterRegionPosX() - lodDim.getWidth() / 2, - zRegion + lodDim.getCenterRegionPosZ() - lodDim.getWidth() / 2); - - // local position in the vbo and bufferBuilder arrays - LodBufferBuilder[] currentBuffers = buildableBuffers[xRegion][zRegion]; - LodRegion region = lodDim.getRegion(regionPos.x, regionPos.z); - - if (region == null) - continue; - - // make sure the buffers weren't - // changed while we were running this method - if (currentBuffers == null || !currentBuffers[0].building()) - { - ClientApi.LOGGER.info("Buffer building quit early"); - return; - } - - byte minDetail = region.getMinDetailLevel(); - - - final int xR = xRegion; - final int zR = zRegion; - - //we create the Callable to use for the buffer builder creation - Callable dataToRenderThread = () -> - { - //Variable initialization - byte detailLevel; - int posX; - int posZ; - int xAdj; - int zAdj; - int bufferIndex; - boolean posNotInPlayerChunk; - boolean adjPosInPlayerChunk; - VertexOptimizer vertexOptimizer = ThreadMapUtil.getBox(); - boolean[] adjShadeDisabled = ThreadMapUtil.getAdjShadeDisabledArray(); - - // determine how many LODs we can stack vertically - int maxVerticalData = DetailDistanceUtil.getMaxVerticalData((byte) 0); - - //we get or create the map that will contain the adj data - Map adjData = ThreadMapUtil.getAdjDataArray(maxVerticalData); - - //previous setToRender cache - if (setsToRender[xR][zR] == null) - setsToRender[xR][zR] = new PosToRenderContainer(minDetail, regionPos.x, regionPos.z); - - - //We ask the lod dimension which block we have to render given the player position - PosToRenderContainer posToRender = setsToRender[xR][zR]; - posToRender.clear(minDetail, regionPos.x, regionPos.z); - - lodDim.getPosToRender( - posToRender, - regionPos, - playerX, - playerZ); - - - - // keep a local version, so we don't have to worry about indexOutOfBounds Exceptions - // if it changes in the LodRenderer while we are working here - // FIXME: THIS IS NOT HOW IT WORKS! We also can't just loop and copy it. Think of an - // idea to fix this! - boolean[][] vanillaRenderedChunks = renderer.vanillaRenderedChunks; - short gameChunkRenderDistance = (short) (vanillaRenderedChunks.length / 2 - 1); - - for (int index = 0; index < posToRender.getNumberOfPos(); index++) - { - bufferIndex = index % currentBuffers.length; - detailLevel = posToRender.getNthDetailLevel(index); - posX = posToRender.getNthPosX(index); - posZ = posToRender.getNthPosZ(index); - - int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkX; - int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkZ; - - // FIXME: We don't need to ignore rendered chunks! Just build it and leave it for the renderer to decide! - //We don't want to render this fake block if - //The block is inside the render distance with, is not bigger than a chunk and is positioned in a chunk set as vanilla rendered - // - //The block is in the player chunk or in a chunk adjacent to the player - if(isThisPositionGoingToBeRendered(detailLevel, posX, posZ, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance)) - { - continue; - } - - //we check if the block to render is not in player chunk - posNotInPlayerChunk = !(chunkXdist == 0 && chunkZdist == 0); - - // We extract the adj data in the four cardinal direction - - // we first reset the adjShadeDisabled. This is used to disable the shade on the border when we have transparent block like water or glass - // to avoid having a "darker border" underground - Arrays.fill(adjShadeDisabled, false); - - //We check every adj block in each direction - for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS) - { - - xAdj = posX + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).x; - zAdj = posZ + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).z; - long data; - chunkXdist = LevelPosUtil.getChunkPos(detailLevel, xAdj) - playerChunkX; - chunkZdist = LevelPosUtil.getChunkPos(detailLevel, zAdj) - playerChunkZ; - adjPosInPlayerChunk = (chunkXdist == 0 && chunkZdist == 0); - - //If the adj block is rendered in the same region and with same detail - // and is positioned in a place that is not going to be rendered by vanilla game - // then we can set this position as adj - // We avoid cases where the adjPosition is in player chunk while the position is not - // to always have a wall underwater - if(posToRender.contains(detailLevel, xAdj, zAdj) - && !isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance) - && !(posNotInPlayerChunk && adjPosInPlayerChunk)) - { - for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, xAdj, zAdj); verticalIndex++) - { - data = lodDim.getData(detailLevel, xAdj, zAdj, verticalIndex); - adjShadeDisabled[VertexOptimizer.DIRECTION_INDEX.get(lodDirection)] = false; - adjData.get(lodDirection)[verticalIndex] = data; - } - } - else - { - //Otherwise, we check if this position is - data = lodDim.getSingleData(detailLevel, xAdj, zAdj); - - adjData.get(lodDirection)[0] = DataPointUtil.EMPTY_DATA; - - if ((isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance) || (posNotInPlayerChunk && adjPosInPlayerChunk)) - && !DataPointUtil.isVoid(data)) - { - adjShadeDisabled[VertexOptimizer.DIRECTION_INDEX.get(lodDirection)] = DataPointUtil.getAlpha(data) < 255; - } - } - } - - - // We render every vertical lod present in this position - // We only stop when we find a block that is void or non-existing block - long data; - for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ); verticalIndex++) - { - - //we get the above block as adj UP - if (verticalIndex > 0) - adjData.get(LodDirection.UP)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex - 1); - else - adjData.get(LodDirection.UP)[0] = DataPointUtil.EMPTY_DATA; - - - //we get the below block as adj DOWN - if (verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ) - 1) - adjData.get(LodDirection.DOWN)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex + 1); - else - adjData.get(LodDirection.DOWN)[0] = DataPointUtil.EMPTY_DATA; - - //We extract the data to render - data = lodDim.getData(detailLevel, posX, posZ, verticalIndex); - - //If the data is not renderable (Void or non-existing) we stop since there is no data left in this position - if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data)) - break; - - //We send the call to create the vertices - CubicLodTemplate.addLodToBuffer(currentBuffers[bufferIndex], playerX, playerZ, data, adjData, - detailLevel, posX, posZ, vertexOptimizer, renderer.previousDebugMode, adjShadeDisabled, cullingRangeX, cullingRangeZ); - } - - } // for pos to in list to render - // the thread executed successfully - return true; - }; - - nodeToRenderThreads.add(dataToRenderThread); - + final int regionX = indexX + buildableVbos.getCenterX() - buildableVbos.gridCentreToEdge; + final int regionZ = indexZ + buildableVbos.getCenterY() - buildableVbos.gridCentreToEdge; + + boolean needRegen = lodDim.getAndClearRegionNeedBufferRegen(regionX, regionZ); + needRegen |= fullRegen; + if (!needRegen) continue; + + LodRegion region = lodDim.getRegion(regionX, regionZ); + if (region == null) continue; + + RegionPos regionPos = new RegionPos(regionX, regionZ); + posToUpload.add(regionPos); + LodBufferBuilder[] builder = buildableBuffers.get(regionX, regionZ); + LodVertexBuffer[] vbo = buildableVbos.get(regionX, regionZ); + if (vbo == null) { + setupBuffers(regionX, regionZ); + vbo = buildableVbos.get(regionX, regionZ); } + if (builder == null) { + builder = buildableBuffers.setAndGet(regionX, regionZ, new LodBufferBuilder[vbo.length]); + } + for (int i=0; i { + return makeLodRenderData(lodDim, regionPos, pX, pZ, vboX, vboZ, minDetail, cullingRangeX, cullingRangeZ); + }); } // region z } // region z + + //================================// + // execute the nodeToRenderThreads // + //================================// long executeStart = System.currentTimeMillis(); // wait for all threads to finish @@ -464,22 +351,60 @@ public class LodBufferBuilderFactory for (Future future : futuresBuffer) { // the future will be false if its thread failed + try { if (!future.get()) { - ClientApi.LOGGER.warn("LodBufferBuilder ran into trouble and had to start over."); - break; + ClientApi.LOGGER.error("LodBufferBuilder ran into trouble and had to start over."); + continue; + } + } catch (Exception e) { + ClientApi.LOGGER.error("LodBufferBuilder ran into trouble: "); + e.printStackTrace(); + continue; } } - long executeEnd = System.currentTimeMillis(); + long executeEnd = System.currentTimeMillis(); long endTime = System.currentTimeMillis(); long buildTime = endTime - startTime; long executeTime = executeEnd - executeStart; if (enableLogging) - ClientApi.LOGGER.info("Thread Build("+nodeToRenderThreads.size()+") time: " + buildTime + " ms" + '\n' + + ClientApi.LOGGER.info("Thread Build("+nodeToRenderThreads.size()+"/"+(lodDim.getWidth()*lodDim.getWidth())+ (fullRegen ? "FULL" : "")+") time: " + buildTime + " ms" + '\n' + "thread execute time: " + executeTime + " ms"); + + //================================// + // upload the new data // + //================================// + try + { + long startUploadTime = System.currentTimeMillis(); + // clean up any potentially open resources + for (RegionPos regPos : posToUpload) { + LodBufferBuilder[] buffers = buildableBuffers.get(regPos.x, regPos.z); + if (buffers == null) continue; + for (LodBufferBuilder buffer : buffers) { + try { + buffer.end(); + } catch (Exception e) { + ClientApi.LOGGER.error("\"LodNodeBufferBuilder\" was unable to close buildable buffer: " + e.getMessage()); + e.printStackTrace(); + } + } + } + + // upload the new buffers + uploadBuffers(posToUpload); + long uploadTime = System.currentTimeMillis() - startUploadTime; + if (enableLogging) + ClientApi.LOGGER.info("Thread Upload time: " + uploadTime + " ms"); + } + catch (Exception e) + { + ClientApi.LOGGER.error("\"LodNodeBufferBuilder.generateLodBuffersAsync\" was unable to upload the buffers to the GPU: " + e.getMessage()); + e.printStackTrace(); + } // mark that the buildable buffers as ready to swap switchVbos = true; } @@ -490,35 +415,151 @@ public class LodBufferBuilderFactory } finally { - try - { - long startUploadTime = System.currentTimeMillis(); - // clean up any potentially open resources - if (buildableBuffers != null) - closeBuffers(fullRegen, lodDim); - - // upload the new buffers - uploadBuffers(fullRegen, lodDim); - long uploadTime = System.currentTimeMillis() - startUploadTime; - if (enableLogging) - ClientApi.LOGGER.info("Thread Upload time: " + uploadTime + " ms"); - } - catch (Exception e) - { - ClientApi.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" was unable to upload the buffers to the GPU: " + e.getMessage()); - e.printStackTrace(); - } - // regardless of whether we were able to successfully create // the buffers, we are done generating. generatingBuffers = false; + glProxy.setGlContext(oldContext); bufferLock.unlock(); } } + private boolean makeLodRenderData(LodDimension lodDim, RegionPos regPos, int playerX, int playerZ, + int vboX, int vboZ, byte minDetail, int cullingRangeX, int cullingRangeZ) { + + //Variable initialization + int playerChunkX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerX,LodUtil.CHUNK_DETAIL_LEVEL); + int playerChunkZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerZ,LodUtil.CHUNK_DETAIL_LEVEL); + DebugMode debugMode = CONFIG.client().advanced().debugging().getDebugMode(); + VertexOptimizer vertexOptimizer = ThreadMapUtil.getBox(); + boolean[] adjShadeDisabled = ThreadMapUtil.getAdjShadeDisabledArray(); + LodBufferBuilder[] currentBuffers = buildableBuffers.get(regPos.x, regPos.z); + + // determine how many LODs we can stack vertically + int maxVerticalData = DetailDistanceUtil.getMaxVerticalData((byte) 0); + + //we get or create the map that will contain the adj data + Map adjData = ThreadMapUtil.getAdjDataArray(maxVerticalData); + + //We ask the lod dimension which block we have to render given the player position + PosToRenderContainer posToRender = setsToRender.get(regPos.x, regPos.z); + //previous setToRender cache + if (posToRender == null) { + posToRender = setsToRender.setAndGet(regPos.x, regPos.z, new PosToRenderContainer(minDetail, regPos.x, regPos.z)); + } + posToRender.clear(minDetail, regPos.x, regPos.z); + lodDim.getPosToRender(posToRender, regPos, playerX, playerZ); + + for (int index = 0; index < posToRender.getNumberOfPos(); index++) + { + int bufferIndex = index % currentBuffers.length; + byte detailLevel = posToRender.getNthDetailLevel(index); + int posX = posToRender.getNthPosX(index); + int posZ = posToRender.getNthPosZ(index); + + int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkX; + int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkZ; + + // Currently fixing below + // FIXME: We don't need to ignore rendered chunks! Just build it and leave it for the renderer to decide! + //We don't want to render this fake block if + //The block is inside the render distance with, is not bigger than a chunk and is positioned in a chunk set as vanilla rendered + // + //The block is in the player chunk or in a chunk adjacent to the player + //if(isThisPositionGoingToBeRendered(detailLevel, posX, posZ, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance)) + //{ + // continue; + //} + + //we check if the block to render is not in player chunk + boolean posNotInPlayerChunk = !(chunkXdist == 0 && chunkZdist == 0); + + // We extract the adj data in the four cardinal direction + + // we first reset the adjShadeDisabled. This is used to disable the shade on the border when we have transparent block like water or glass + // to avoid having a "darker border" underground + Arrays.fill(adjShadeDisabled, false); + + //We check every adj block in each direction + for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS) + { + int xAdj = posX + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).x; + int zAdj = posZ + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).z; + chunkXdist = LevelPosUtil.getChunkPos(detailLevel, xAdj) - playerChunkX; + chunkZdist = LevelPosUtil.getChunkPos(detailLevel, zAdj) - playerChunkZ; + boolean adjPosInPlayerChunk = (chunkXdist == 0 && chunkZdist == 0); + + //If the adj block is rendered in the same region and with same detail + // and is positioned in a place that is not going to be rendered by vanilla game + // then we can set this position as adj + // We avoid cases where the adjPosition is in player chunk while the position is not + // to always have a wall underwater + if(posToRender.contains(detailLevel, xAdj, zAdj) + //&& !isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance) + && !(posNotInPlayerChunk && adjPosInPlayerChunk)) + { + for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, xAdj, zAdj); verticalIndex++) + { + long data = lodDim.getData(detailLevel, xAdj, zAdj, verticalIndex); + adjShadeDisabled[VertexOptimizer.DIRECTION_INDEX.get(lodDirection)] = false; + adjData.get(lodDirection)[verticalIndex] = data; + } + } + else + { + //Otherwise, we check if this position is + long data = lodDim.getSingleData(detailLevel, xAdj, zAdj); + + adjData.get(lodDirection)[0] = DataPointUtil.EMPTY_DATA; + + if ((//isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance) || + (posNotInPlayerChunk && adjPosInPlayerChunk)) + && !DataPointUtil.isVoid(data)) + { + adjShadeDisabled[VertexOptimizer.DIRECTION_INDEX.get(lodDirection)] = DataPointUtil.getAlpha(data) < 255; + } + } + } + + // We render every vertical lod present in this position + // We only stop when we find a block that is void or non-existing block + long data; + for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ); verticalIndex++) + { + + //we get the above block as adj UP + if (verticalIndex > 0) + adjData.get(LodDirection.UP)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex - 1); + else + adjData.get(LodDirection.UP)[0] = DataPointUtil.EMPTY_DATA; + + + //we get the below block as adj DOWN + if (verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ) - 1) + adjData.get(LodDirection.DOWN)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex + 1); + else + adjData.get(LodDirection.DOWN)[0] = DataPointUtil.EMPTY_DATA; + + //We extract the data to render + data = lodDim.getData(detailLevel, posX, posZ, verticalIndex); + + //If the data is not renderable (Void or non-existing) we stop since there is no data left in this position + if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data)) + break; + + //We send the call to create the vertices + CubicLodTemplate.addLodToBuffer(currentBuffers[bufferIndex], vboX, vboZ, data, adjData, + detailLevel, posX, posZ, vertexOptimizer, debugMode, adjShadeDisabled, cullingRangeX, cullingRangeZ); + } + + } // for pos to in list to render + // the thread executed successfully + return true; + } + + + + @Deprecated private boolean isThisPositionGoingToBeRendered(byte detailLevel, int posX, int posZ, int chunkPosX, int chunkPosZ, boolean[][] vanillaRenderedChunks, int gameChunkRenderDistance){ - - // skip any chunks that Minecraft is going to render int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - chunkPosX; int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - chunkPosZ; @@ -557,125 +598,27 @@ public class LodBufferBuilderFactory *

* May have to wait for the bufferLock to open. */ - public void setupBuffers(LodDimension lodDimension) - { - try - { - bufferLock.lock(); - - int numbRegionsWide = lodDimension.getWidth(); - long regionMemoryRequired; - int numberOfBuffers; - - GLProxy glProxy = GLProxy.getInstance(); - GLProxyContext oldContext = glProxy.getGlContext(); - glProxy.setGlContext(GLProxyContext.LOD_BUILDER); - - - previousRegionWidth = numbRegionsWide; - numberOfBuffersPerRegion = new int[numbRegionsWide][numbRegionsWide]; - buildableBuffers = new LodBufferBuilder[numbRegionsWide][numbRegionsWide][]; - - buildableVbos = new LodVertexBuffer[numbRegionsWide][numbRegionsWide][]; - drawableVbos = new LodVertexBuffer[numbRegionsWide][numbRegionsWide][]; - - if (glProxy.bufferStorageSupported) - { - buildableStorageBufferIds = new int[numbRegionsWide][numbRegionsWide][]; - drawableStorageBufferIds = new int[numbRegionsWide][numbRegionsWide][]; + private void setupBuffers(int regionX, int regionZ) { + //TODO: Impl actual multibuffers + //int regionMemoryRequired = DEFAULT_MEMORY_ALLOCATION; + //int numberOfBuffers; + LodVertexBuffer[] vbos = buildableVbos.get(regionX, regionZ); + if (vbos != null) + for (LodVertexBuffer vbo : vbos) { + if (vbo!=null) vbo.close(); } - - for (int x = 0; x < numbRegionsWide; x++) - { - for (int z = 0; z < numbRegionsWide; z++) - { - regionMemoryRequired = DEFAULT_MEMORY_ALLOCATION; - - // if the memory required is greater than the max buffer - // capacity, divide the memory across multiple buffers - if (regionMemoryRequired > LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY) - { - numberOfBuffers = (int) regionMemoryRequired / LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY + 1; - - // TODO shouldn't this be determined with regionMemoryRequired? - // always allocating the max memory is a bit expensive isn't it? - regionMemoryRequired = LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY; - numberOfBuffersPerRegion[x][z] = numberOfBuffers; - buildableBuffers[x][z] = new LodBufferBuilder[numberOfBuffers]; - buildableVbos[x][z] = new LodVertexBuffer[numberOfBuffers]; - drawableVbos[x][z] = new LodVertexBuffer[numberOfBuffers]; - - if (glProxy.bufferStorageSupported) - { - buildableStorageBufferIds[x][z] = new int[numberOfBuffers]; - drawableStorageBufferIds[x][z] = new int[numberOfBuffers]; - } - } - else - { - // we only need one buffer for this region - numberOfBuffersPerRegion[x][z] = 1; - buildableBuffers[x][z] = new LodBufferBuilder[1]; - buildableVbos[x][z] = new LodVertexBuffer[1]; - drawableVbos[x][z] = new LodVertexBuffer[1]; - - if (glProxy.bufferStorageSupported) - { - buildableStorageBufferIds[x][z] = new int[1]; - drawableStorageBufferIds[x][z] = new int[1]; - } - } - - - for (int i = 0; i < numberOfBuffersPerRegion[x][z]; i++) - { - buildableBuffers[x][z][i] = new LodBufferBuilder((int) regionMemoryRequired); - - buildableVbos[x][z][i] = new LodVertexBuffer(); - drawableVbos[x][z][i] = new LodVertexBuffer(); - - - // create the initial mapped buffers (system memory) - GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, buildableVbos[x][z][i].id); - GL32.glBufferData(GL32.GL_ARRAY_BUFFER, regionMemoryRequired, GL32.GL_STATIC_DRAW); - GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0); - - GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, drawableVbos[x][z][i].id); - GL32.glBufferData(GL32.GL_ARRAY_BUFFER, regionMemoryRequired, GL32.GL_STATIC_DRAW); - GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0); - - - if (glProxy.bufferStorageSupported) - { - // create the buffer storage (GPU memory) - buildableStorageBufferIds[x][z][i] = GL44.glGenBuffers(); - GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, buildableStorageBufferIds[x][z][i]); - GL44.glBufferStorage(GL44.GL_ARRAY_BUFFER, regionMemoryRequired, GL44.GL_DYNAMIC_STORAGE_BIT); - GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, 0); - - drawableStorageBufferIds[x][z][i] = GL44.glGenBuffers(); - GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, drawableStorageBufferIds[x][z][i]); - GL44.glBufferStorage(GL44.GL_ARRAY_BUFFER, regionMemoryRequired, GL44.GL_DYNAMIC_STORAGE_BIT); - GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, 0); - } - } - } - } - - glProxy.setGlContext(oldContext); - } - catch (Exception e) + /* + if (regionMemoryRequired > LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY) { - ClientApi.LOGGER.info("setupBuffers ran into trouble: " + e.getMessage(), e); - } - finally - { - // this shouldn't normally happen, but just in case it sill prevent deadlock - bufferLock.unlock(); - } + // TODO shouldn't this be determined with regionMemoryRequired? + // always allocating the max memory is a bit expensive isn't it? + numberOfBuffers = (int) regionMemoryRequired / LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY + 1; + } else { + numberOfBuffers = 1; + }*/ + vbos = buildableVbos.setAndGet(regionX, regionZ, new LodVertexBuffer[1]); } - /** * Sets the buffers and Vbos to null, forcing them to be recreated
* and destroys any bound OpenGL objects.
@@ -684,18 +627,12 @@ public class LodBufferBuilderFactory * May have to wait for the bufferLock to open. */ public void destroyBuffers() { - int[][][] toBeDeletedBuildableStorageBufferIds; - int[][][] toBeDeletedDrawableStorageBufferIds; - LodVertexBuffer[][][] toBeDeletedBuildableVbos; - LodVertexBuffer[][][] toBeDeletedDrawableVbos; + MovableGridList toBeDeletedBuildableVbos; + MovableGridList toBeDeletedDrawableVbos; bufferLock.lock(); try { - toBeDeletedBuildableStorageBufferIds = buildableStorageBufferIds; - toBeDeletedDrawableStorageBufferIds = drawableStorageBufferIds; toBeDeletedBuildableVbos = buildableVbos; toBeDeletedDrawableVbos = drawableVbos; - buildableStorageBufferIds = null; - drawableStorageBufferIds = null; buildableVbos = null; drawableVbos = null; // these don't contain any OpenGL objects, so @@ -704,346 +641,255 @@ public class LodBufferBuilderFactory } finally { bufferLock.unlock(); } - // make sure the buffers are deleted in a openGL context GLProxy.getInstance().recordOpenGlCall(() -> { - - // destroy the buffer storages if they aren't already - if (toBeDeletedBuildableStorageBufferIds != null) { - for (int x = 0; x < toBeDeletedBuildableStorageBufferIds.length; x++) { - for (int z = 0; z < toBeDeletedBuildableStorageBufferIds.length; z++) { - for (int i = 0; i < toBeDeletedBuildableStorageBufferIds[x][z].length; i++) { - int buildableId = toBeDeletedBuildableStorageBufferIds[x][z][i]; - int drawableId = toBeDeletedDrawableStorageBufferIds[x][z][i]; - - GL32.glDeleteBuffers(buildableId); - GL32.glDeleteBuffers(drawableId); - - } + // destroy the VBOs if they aren't already + if (toBeDeletedBuildableVbos != null) { + for (LodVertexBuffer[] vbos : toBeDeletedBuildableVbos) { + if (vbos == null) continue; + for (LodVertexBuffer vbo : vbos) { + if (vbo == null) continue; + vbo.close(); } } } - // destroy the VBOs if they aren't already - if (toBeDeletedBuildableVbos != null) { - for (int i = 0; i < toBeDeletedBuildableVbos.length; i++) { - for (int j = 0; j < toBeDeletedBuildableVbos.length; j++) { - for (int k = 0; k < toBeDeletedBuildableVbos[i][j].length; k++) { - if (toBeDeletedBuildableVbos[i][j][k] != null) { - int buildableId = toBeDeletedBuildableVbos[i][j][k].id; - GL32.glDeleteBuffers(buildableId); - } - if (toBeDeletedDrawableVbos[i][j][k] != null) { - int drawableId = toBeDeletedDrawableVbos[i][j][k].id; - GL32.glDeleteBuffers(drawableId); - } - } + if (toBeDeletedDrawableVbos != null) { + for (LodVertexBuffer[] vbos : toBeDeletedDrawableVbos) { + if (vbos == null) continue; + for (LodVertexBuffer vbo : vbos) { + if (vbo == null) continue; + vbo.close(); } } } }); } - - /** Calls begin on each of the buildable BufferBuilders. */ - private void startBuffers(boolean fullRegen, LodDimension lodDim) + + /** Upload all buildableBuffers to the GPU. We should already be in the builder context */ + private void uploadBuffers(List toBeUploaded) { - for (int x = 0; x < buildableBuffers.length; x++) - { - for (int z = 0; z < buildableBuffers.length; z++) + GLProxy glProxy = GLProxy.getInstance(); + // determine the upload method + GpuUploadMethod uploadMethod = glProxy.getGpuUploadMethod(); + + // determine the upload timeout + int MBPerMS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds(); + long BPerNS = MBPerMS; // 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 bytesUploaded = 0; + + // actually upload the buffers + for (RegionPos p : toBeUploaded) { + LodBufferBuilder[] buffers = buildableBuffers.get(p.x, p.z); + for (int i = 0; i < buffers.length; i++) { - if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z)) - { - for (int i = 0; i < buildableBuffers[x][z].length; i++) - { - // FIXME: for some reason BufferBuilder.vertexCounts - // isn't reset unless this is called, which can cause - // a false indexOutOfBoundsException - buildableBuffers[x][z][i].discard(); - - buildableBuffers[x][z][i].begin(GL32.GL_QUADS, LodUtil.LOD_VERTEX_FORMAT); - } + ByteBuffer uploadBuffer = null; + //FIXME: The sonme Buffers aren't closed/end() and causing errors! + try { + LagSpikeCatcher b = new LagSpikeCatcher(); + uploadBuffer = buffers[i].getCleanedByteBuffer(); + b.end("getCleanedByteBuffer"); + } catch (IndexOutOfBoundsException e) { + // NOTE: Temp try/catch for above FIXME. + e.printStackTrace(); + } catch (RuntimeException e) { + ClientApi.LOGGER.error(LodBufferBuilderFactory.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage()); + e.printStackTrace(); + } + if (uploadBuffer == null) continue; + LagSpikeCatcher vboU = new LagSpikeCatcher(); + vboUpload(p, i, uploadBuffer, uploadMethod); + vboU.end("vboUpload"); + + // upload buffers over an extended period of time + // to hopefully prevent stuttering. + remainingNS += uploadBuffer.capacity()*BPerNS; + bytesUploaded += uploadBuffer.capacity(); + 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; } } } + ClientApi.LOGGER.info("UploadBuffers uploaded "+bytesUploaded+" bytes."); } - /** Calls end on each of the buildable BufferBuilders. */ - private void closeBuffers(boolean fullRegen, LodDimension lodDim) + /** Uploads the uploadBuffer so the GPU can use it. */ + private void vboUpload(RegionPos regPos, int iIndex, ByteBuffer uploadBuffer, GpuUploadMethod uploadMethod) { - for (int x = 0; x < buildableBuffers.length; x++) - for (int z = 0; z < buildableBuffers.length; z++) - for (int i = 0; i < buildableBuffers[x][z].length; i++) - if (buildableBuffers[x][z][i] != null && buildableBuffers[x][z][i].building() && (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z))) - buildableBuffers[x][z][i].end(); - } - - - /** Upload all buildableBuffers to the GPU. */ - private void uploadBuffers(boolean fullRegen, LodDimension lodDim) - { - GLProxy glProxy = GLProxy.getInstance(); - try - { - // make sure we are uploading to the builder context, - // this helps prevent interference (IE stuttering) with the Minecraft context. - glProxy.setGlContext(GLProxyContext.LOD_BUILDER); - - // determine the upload method - GpuUploadMethod uploadMethod = glProxy.getGpuUploadMethod(); - - // determine the upload timeout - int MBPerMS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds(); - long BPerNS = MBPerMS; // 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. - - // actually upload the buffers - for (int x = 0; x < buildableVbos.length; x++) - { - for (int z = 0; z < buildableVbos.length; z++) - { - if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z)) - { - for (int i = 0; i < buildableBuffers[x][z].length; i++) - { - ByteBuffer uploadBuffer = null; - //FIXME: The sonme Buffers aren't closed/end() and causing errors! - try { - LagSpikeCatcher b = new LagSpikeCatcher(); - uploadBuffer = buildableBuffers[x][z][i].getCleanedByteBuffer(); - b.end("getCleanedByteBuffer"); - } catch (IndexOutOfBoundsException e) { - // NOTE: Temp try/catch for above FIXME. - // e.printStackTrace(); - } - if (uploadBuffer == null) continue; - LagSpikeCatcher vboU = new LagSpikeCatcher(); - vboUpload(x,z,i, uploadBuffer, uploadMethod); - vboU.end("vboUpload"); - LagSpikeCatcher setR = new LagSpikeCatcher(); - lodDim.setRegenRegionBufferByArrayIndex(x, z, false); - setR.end("setRegenRegionBufferByArrayIndex"); + boolean useBuffStorage = uploadMethod == GpuUploadMethod.BUFFER_STORAGE; + LodVertexBuffer[] vbos = buildableVbos.get(regPos.x, regPos.z); + + if (vbos[iIndex] == null) { + vbos[iIndex] = new LodVertexBuffer(useBuffStorage); + } else if (vbos[iIndex].isBufferStorage != useBuffStorage) { + vbos[iIndex].close(); + vbos[iIndex] = new LodVertexBuffer(useBuffStorage); + } - // upload buffers over an extended period of time - // to hopefully prevent stuttering. - remainingNS += uploadBuffer.capacity()*BPerNS; - if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000/60, TimeUnit.MILLISECONDS)) { - if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS) remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS; - Thread.sleep(remainingNS/1000000, (int) (remainingNS%1000000)); - remainingNS = 0; - } - } - } + LodVertexBuffer vbo = vbos[iIndex]; + // this is how many points will be rendered + vbo.vertexCount = (uploadBuffer.capacity() / LodUtil.LOD_VERTEX_FORMAT.getByteSize()); + + // If size is zero, just ignore it. + if (uploadBuffer.capacity()==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.capacity() || + size > uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER*BUFFER_EXPANSION_MULTIPLIER) + { + int newSize = (int)(uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER); + 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.capacity() || + size > uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER*BUFFER_EXPANSION_MULTIPLIER) + { + int newSize = (int) (uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER); + 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.capacity(), + 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.capacity(); + 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.capacity() || + size > uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER*BUFFER_EXPANSION_MULTIPLIER) + { + int newSize = (int)(uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER); + 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) { - // this doesn't appear to be necessary anymore, but just in case. - ClientApi.LOGGER.error(LodBufferBuilderFactory.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage()); + ClientApi.LOGGER.error("vboUpload failed: " + e.getClass().getSimpleName()); e.printStackTrace(); - } finally { - // newSingleThreadExecutor doesn't mean that all jobs will be on a single, same - // thread. It just means that it can at most use one thread. If there are no - // jobs for a certain amount of time, or something happened when a job is - // executing, it could decide to delete the thread, and create a new one for the - // next job. So we will need to release the gl context. - LagSpikeCatcher end = new LagSpikeCatcher(); - glProxy.setGlContext(GLProxyContext.NONE); - end.end("GLSwitchContext"); } - } - - /** Uploads the uploadBuffer so the GPU can use it. */ - private void vboUpload(int xIndex, int zIndex, int iIndex, ByteBuffer uploadBuffer, GpuUploadMethod uploadMethod) - { - - - - // get the vbos, buffers, ids, etc. - int storageBufferId = 0; - if (buildableStorageBufferIds != null) - storageBufferId = buildableStorageBufferIds[xIndex][zIndex][iIndex]; - - LodVertexBuffer vbo = buildableVbos[xIndex][zIndex][iIndex]; - - // this is how many points will be rendered - vbo.vertexCount = (uploadBuffer.capacity() / LodUtil.LOD_VERTEX_FORMAT.getByteSize()); - // If size is zero, just ignore it. - if (uploadBuffer.capacity()==0) return; - - // this shouldn't happen, but just to be safe - if (vbo.id != -1 && GLProxy.getInstance().getGlContext() == GLProxyContext.LOD_BUILDER) - { - LagSpikeCatcher bindBuff = new LagSpikeCatcher(); - GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id); - bindBuff.end("glBindBuffer vbo.id"); - - try - { - // if possible use the faster buffer storage route - if (uploadMethod == GpuUploadMethod.BUFFER_STORAGE && storageBufferId != 0) - { - GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, storageBufferId); - - LagSpikeCatcher getParm = new LagSpikeCatcher(); - long size = GL32.glGetBufferParameteri(GL32.GL_ARRAY_BUFFER, GL32.GL_BUFFER_SIZE); - getParm.end("glGetBufferParameteri BuffStorage"); - if (size < uploadBuffer.capacity()) - { - int newSize = (int)(uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER); - LagSpikeCatcher buffResizeRegen = new LagSpikeCatcher(); - GL32.glDeleteBuffers(storageBufferId); - buildableStorageBufferIds[xIndex][zIndex][iIndex] = GL32.glGenBuffers(); - buffResizeRegen.end("glDeleteBuffers BuffStorage resize"); - GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, storageBufferId); - storageBufferId = buildableStorageBufferIds[xIndex][zIndex][iIndex]; - LagSpikeCatcher buffResize = new LagSpikeCatcher(); - GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, newSize, GL44.GL_DYNAMIC_STORAGE_BIT); - 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) - { - // TODO: Check this half reasonable comment! - // 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. - LagSpikeCatcher getParm = new LagSpikeCatcher(); - long size = GL32.glGetBufferParameteri(GL32.GL_ARRAY_BUFFER, GL32.GL_BUFFER_SIZE); - getParm.end("glGetBufferParameteri BuffMapping"); - if (size < uploadBuffer.capacity()) - { - int newSize = (int) (uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER); - LagSpikeCatcher buffResize = new LagSpikeCatcher(); - GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW); - 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.capacity(), - 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); - buffWrite.end("WriteData BuffMapping"); - } - else if (uploadMethod == GpuUploadMethod.DATA) - { - // 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); - buffData.end("glBufferData Data"); - } - else - { - // TODO: Check this nonsense comment! - // hybrid subData/bufferData // - // less stutter, low GPU usage - LagSpikeCatcher getParm = new LagSpikeCatcher(); - long size = GL32.glGetBufferParameteri(GL32.GL_ARRAY_BUFFER, GL32.GL_BUFFER_SIZE); - getParm.end("glGetBufferParameteri SubData"); - if (size < uploadBuffer.capacity()) - { - int newSize = (int)(uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER); - LagSpikeCatcher buffResize = new LagSpikeCatcher(); - GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW); - 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(); - } - finally - { - LagSpikeCatcher buffUnmap = new LagSpikeCatcher(); - if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING) - GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER); - buffUnmap.end("glUnmapBuffer"); - - LagSpikeCatcher buffUnbind = new LagSpikeCatcher(); - GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0); - buffUnbind.end("glBindBuffer 0"); - } - - }//if vbo exists and in correct GL context }//vboUpload - /** Get the newly created VBOs */ - public VertexBuffersAndOffset getVertexBuffers() - { - // don't wait for the lock to open, - // since this is called on the main render thread - // TODO: Use atomic swap instead of locks! - if (bufferLock.tryLock()) + private boolean swapBuffers() { + bufferLock.lock(); + ClientApi.LOGGER.info("Lod Swap Buffers"); { + boolean shouldRegenBuff = true; try { - LodVertexBuffer[][][] tmpVbo = drawableVbos; + MovableGridList tmpVbo = drawableVbos; drawableVbos = buildableVbos; buildableVbos = tmpVbo; + + //ClientApi.LOGGER.info("Lod Swapped Buffers: "+drawableVbos.toDetailString()); - int[][][] tmpStorage = drawableStorageBufferIds; - drawableStorageBufferIds = buildableStorageBufferIds; - buildableStorageBufferIds = tmpStorage; - - drawableCenterChunkPosX = buildableCenterBlockPosX; - drawableCenterChunkPosZ = buildableCenterBlockPosZ; - + 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; + + //FIXME: Race condition on the allBuffersRequireReset boolean + shouldRegenBuff = frontBufferRequireReset || allBuffersRequireReset; + frontBufferRequireReset = allBuffersRequireReset; + allBuffersRequireReset = false; } catch (Exception e) { // this shouldn't normally happen, but just in case it sill prevent deadlock - ClientApi.LOGGER.info("getVertexBuffers ran into trouble: " + e.getMessage(), e); + ClientApi.LOGGER.error("swapBuffers ran into trouble: " + e.getMessage(), e); } finally { bufferLock.unlock(); } - } - - return new VertexBuffersAndOffset(drawableVbos, drawableStorageBufferIds, drawableCenterChunkPosX, drawableCenterChunkPosZ); - } - - /** A simple container to pass multiple objects back in the getVertexBuffers method. */ - public static class VertexBuffersAndOffset - { - public final LodVertexBuffer[][][] vbos; - public final int[][][] storageBufferIds; - public int drawableCenterBlockPosX; - public int drawableCenterBlockPosZ; - - public VertexBuffersAndOffset(LodVertexBuffer[][][] newVbos, int[][][] newStorageBufferIds, int newDrawableCenterBlockPosX, int newDrawableCenterBlockPosZ) - { - vbos = newVbos; - storageBufferIds = newStorageBufferIds; - drawableCenterBlockPosX = newDrawableCenterBlockPosX; - drawableCenterBlockPosZ = newDrawableCenterBlockPosZ; + return shouldRegenBuff; } } - /** - * If this is true the buildable near and far - * buffers have been generated and are ready to be - * sent to the LodRenderer. - */ - public boolean newBuffersAvailable() + /** Get the newly created VBOs */ + public MovableGridList getFrontBuffers() { - return switchVbos; + return drawableVbos; + } + public int getFrontBuffersCenterX() + { + return drawableCenterBlockX; + } + public int getFrontBuffersCenterZ() + { + return drawableCenterBlockZ; } } diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java index 7ac88a57e..30d5c97c4 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java @@ -78,9 +78,9 @@ public class LodDimension /** stores if the region at the given x and z index needs to be saved to disk */ private volatile boolean[][] isRegionDirty; /** stores if the region at the given x and z index needs to be regenerated */ - private volatile boolean[][] regenRegionBuffer; - /** stores if the buffer size at the given x and z index needs to be changed */ - private volatile boolean[][] recreateRegionBuffer; + // Use int because I need Tri state: + // 0: both buffer good. 1: the displaying buffer good. 2: both buffer bad. + private volatile int[][] regenRegionBuffer; /** * if true that means there are regions in this dimension @@ -143,13 +143,14 @@ public class LodDimension regions = new LodRegion[width][width]; isRegionDirty = new boolean[width][width]; - regenRegionBuffer = new boolean[width][width]; - recreateRegionBuffer = new boolean[width][width]; + regenRegionBuffer = new int[width][width]; center = new RegionPos(0, 0); } + + //FIXME: Race condition on this move and other reading regions! /** * Move the center of this LodDimension and move all owned * regions over by the given x and z offset.

@@ -167,9 +168,10 @@ public class LodDimension if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width) { for (int x = 0; x < width; x++) - for (int z = 0; z < width; z++) + for (int z = 0; z < width; z++) { regions[x][z] = null; - + regenRegionBuffer[x][z] = 0; + } // update the new center center.x += xOffset; center.z += zOffset; @@ -186,10 +188,14 @@ public class LodDimension { for (int z = 0; z < width; z++) { - if (x + xOffset < width) + if (x + xOffset < width) { regions[x][z] = regions[x + xOffset][z]; - else + regenRegionBuffer[x][z] = regenRegionBuffer[x + xOffset][z]; + } + else { regions[x][z] = null; + regenRegionBuffer[x][z] = 0; + } } } } @@ -200,11 +206,14 @@ public class LodDimension { for (int z = 0; z < width; z++) { - if (x + xOffset >= 0) + if (x + xOffset >= 0) { regions[x][z] = regions[x + xOffset][z]; - else + regenRegionBuffer[x][z] = regenRegionBuffer[x + xOffset][z]; + } + else { regions[x][z] = null; - } + regenRegionBuffer[x][z] = 0; + } } } @@ -217,10 +226,14 @@ public class LodDimension { for (int z = 0; z < width; z++) { - if (z + zOffset < width) + if (z + zOffset < width) { regions[x][z] = regions[x][z + zOffset]; - else + regenRegionBuffer[x][z] = regenRegionBuffer[x][z + zOffset]; + } + else { regions[x][z] = null; + regenRegionBuffer[x][z] = 0; + } } } } @@ -231,10 +244,15 @@ public class LodDimension { for (int z = width - 1; z >= 0; z--) { - if (z + zOffset >= 0) + if (z + zOffset >= 0) { regions[x][z] = regions[x][z + zOffset]; - else + regenRegionBuffer[x][z] = regenRegionBuffer[x][z + zOffset]; + } + else { regions[x][z] = null; + regenRegionBuffer[x][z] = 0; + } + } } } } @@ -376,7 +394,8 @@ public class LodDimension if (regions[x][z].getMinDetailLevel() > minAllowedDetailLevel) { regions[x][z].cutTree(minAllowedDetailLevel); - recreateRegionBuffer[x][z] = true; + regenRegionBuffer[x][z] = 2; + regenDimensionBuffers = true; } } }); @@ -430,16 +449,16 @@ public class LodDimension if (regions[x][z] == null) regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode, verticalQuality); - regenRegionBuffer[x][z] = true; + regenRegionBuffer[x][z] = 2; regenDimensionBuffers = true; - recreateRegionBuffer[x][z] = true; } else if (region.getMinDetailLevel() > levelToGen) { // Second case, the region exists at a higher detail level. // Expand the region by introducing the missing layer region.growTree(levelToGen); regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality); - recreateRegionBuffer[x][z] = true; + regenRegionBuffer[x][z] = 2; + regenDimensionBuffers = true; } }); }; @@ -476,7 +495,7 @@ public class LodDimension int zIndex = (regionPosZ - center.z) + halfWidth; isRegionDirty[xIndex][zIndex] = true; - regenRegionBuffer[xIndex][zIndex] = true; + regenRegionBuffer[xIndex][zIndex] = 2; regenDimensionBuffers = true; } catch (ArrayIndexOutOfBoundsException e) @@ -518,7 +537,7 @@ public class LodDimension int zIndex = (regionPosZ - center.z) + halfWidth; isRegionDirty[xIndex][zIndex] = true; - regenRegionBuffer[xIndex][zIndex] = true; + regenRegionBuffer[xIndex][zIndex] = 2; regenDimensionBuffers = true; } catch (ArrayIndexOutOfBoundsException e) @@ -538,7 +557,7 @@ public class LodDimension { int xIndex = (xRegion - center.x) + halfWidth; int zIndex = (zRegion - center.z) + halfWidth; - regenRegionBuffer[xIndex][zIndex] = true; + regenRegionBuffer[xIndex][zIndex] = 2; } /** @@ -760,31 +779,35 @@ public class LodDimension { if (detailLevel > LodUtil.REGION_DETAIL_LEVEL) throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); - - LodRegion region = getRegion(detailLevel, posX, posZ); + + int xRegion = LevelPosUtil.getRegion(detailLevel, posX); + int zRegion = LevelPosUtil.getRegion(detailLevel, posZ); + LodRegion region = getRegion(xRegion, zRegion); if (region == null) return; - + markRegionBufferToRegen(xRegion, zRegion); region.clear(detailLevel, posX, posZ); } /** * Returns if the buffer at the given array index needs - * to have its buffer regenerated. + * to have its buffer regenerated. Also decrease the state by 1 */ - public boolean doesRegionNeedBufferRegen(int xIndex, int zIndex) + public boolean getAndClearRegionNeedBufferRegen(int regionX, int regionZ) { - return regenRegionBuffer[xIndex][zIndex] || recreateRegionBuffer[xIndex][zIndex]; - } - - - /** - * Sets if the buffer at the given array index needs - * to have its buffer regenerated. - */ - public void setRegenRegionBufferByArrayIndex(int xIndex, int zIndex, boolean newRegen) - { - regenRegionBuffer[xIndex][zIndex] = newRegen; + //FIXME: Use actual atomics on regenRegionBuffer + //FIXME: Race condition on lodDim move/resize! + int xIndex = (regionX - center.x) + halfWidth; + int zIndex = (regionZ - center.z) + halfWidth; + + if (xIndex < 0 || xIndex >= width || zIndex < 0 || zIndex >= width) + return false; + int i = regenRegionBuffer[xIndex][zIndex]; + if (i > 0) { + regenRegionBuffer[xIndex][zIndex]--; + return true; + } + return false; } /** @@ -798,10 +821,13 @@ public class LodDimension { if (detailLevel > LodUtil.REGION_DETAIL_LEVEL) throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); - - LodRegion region = getRegion(detailLevel, posX, posZ); + + int xRegion = LevelPosUtil.getRegion(detailLevel, posX); + int zRegion = LevelPosUtil.getRegion(detailLevel, posZ); + LodRegion region = getRegion(xRegion, zRegion); if (region == null) return; + markRegionBufferToRegen(xRegion, zRegion); region.updateArea(detailLevel, posX, posZ); } @@ -882,8 +908,7 @@ public class LodDimension regions = new LodRegion[width][width]; isRegionDirty = new boolean[width][width]; - regenRegionBuffer = new boolean[width][width]; - recreateRegionBuffer = new boolean[width][width]; + regenRegionBuffer = new int[width][width]; // populate isRegionDirty for (int i = 0; i < width; i++) diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java index afb88127c..4bf6e94b5 100644 --- a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java +++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java @@ -35,13 +35,15 @@ public class LodVertexBuffer implements AutoCloseable { public int id; public int vertexCount; + public final boolean isBufferStorage; + public long size = 0; - public LodVertexBuffer() + public LodVertexBuffer(boolean isBufferStorage) { 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; } diff --git a/src/main/java/com/seibel/lod/core/render/LodRenderer.java b/src/main/java/com/seibel/lod/core/render/LodRenderer.java index 8ee46b275..3d94d14a7 100644 --- a/src/main/java/com/seibel/lod/core/render/LodRenderer.java +++ b/src/main/java/com/seibel/lod/core/render/LodRenderer.java @@ -20,15 +20,14 @@ package com.seibel.lod.core.render; import java.awt.Color; -import java.util.HashSet; +import java.time.Duration; +import java.util.Set; import org.lwjgl.opengl.GL32; import com.seibel.lod.core.api.ApiShared; import com.seibel.lod.core.api.ClientApi; import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory; -import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory.VertexBuffersAndOffset; -import com.seibel.lod.core.enums.config.GpuUploadMethod; import com.seibel.lod.core.enums.rendering.DebugMode; import com.seibel.lod.core.enums.rendering.FogColorMode; import com.seibel.lod.core.enums.rendering.FogDistance; @@ -40,9 +39,11 @@ import com.seibel.lod.core.objects.math.Vec3f; import com.seibel.lod.core.objects.opengl.LodVertexBuffer; 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.GridList; 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.wrapperInterfaces.block.AbstractBlockPosWrapper; import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper; import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; @@ -58,11 +59,40 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper; */ public class LodRenderer { + public static class VanillaRenderedChunksList extends GridList { + private static final long serialVersionUID = -5448501880911391315L; + + public final int centerX; + public final int centerZ; + + public VanillaRenderedChunksList(int range, int centerX, int centerZ) { + super(range); + this.centerX = centerX; + this.centerZ = centerZ; + for (int i=0; i 16000000) { //16 ms + ClientApi.LOGGER.info("NOTE: "+source+" took "+Duration.ofNanos(timer)+"!"); + } + + } + } + private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class); private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); private static final IReflectionHandler REFLECTION_HANDLER = SingletonHandler.get(IReflectionHandler.class); - + + public static final int VANILLA_REFRESH_TIMEOUT = 60; /** * If true the LODs colors will be replaced with * a checkerboard, this can be used for debugging. @@ -75,23 +105,11 @@ public class LodRenderer /** This is used to generate the buildable buffers */ private final LodBufferBuilderFactory lodBufferBuilderFactory; - /** Each VertexBuffer represents 1 region */ - private LodVertexBuffer[][][] vbos; - /** - * the OpenGL IDs for the vbos of the same indices. - * These have to be separate because we can't override the - * buffers in the VBOs (and we don't want to) - */ - private int[][][] storageBufferIds = null; - // The shader program LodRenderProgram shaderProgram = null; - private int vbosCenterX = 0; - private int vbosCenterZ = 0; - /** This is used to determine if the LODs should be regenerated */ - private int[] previousPos = new int[] { 0, 0, 0 }; + private AbstractBlockPosWrapper previousPos = null; // these variables are used to determine if the buffers should be rebuilt private int prevRenderDistance = 0; @@ -115,9 +133,10 @@ public class LodRenderer * This HashSet contains every chunk that Vanilla Minecraft * is going to render */ - public boolean[][] vanillaRenderedChunks; - public boolean vanillaRenderedChunksChanged; - public boolean vanillaRenderedChunksEmptySkip = false; + public VanillaRenderedChunksList vanillaRenderedChunks; + public int vanillaRenderedChunksCenterX; + public int vanillaRenderedChunksCenterZ; + public int vanillaRenderedChunksRefreshTimer; private boolean canVanillaFogBeDisabled = true; @@ -149,8 +168,6 @@ public class LodRenderer return; } - - if (MC_RENDER.playerHasBlindnessEffect()) { // if the player is blind, don't render LODs, @@ -175,8 +192,17 @@ public class LodRenderer // TODO move the buffer regeneration logic into its own class (probably called in the client api instead) // starting here... - determineIfLodsShouldRegenerate(lodDim, partialTicks); + LagSpikeCatcher updateStatue = new LagSpikeCatcher(); + updateRegenStatus(lodDim, partialTicks); + updateStatue.end("LodDrawSetup:UpdateStatus"); + + // FIXME: Currently, we check for last Lod Dimension so that we can trigger a cleanup() if dimension has changed + // The better thing to do is to call cleanup() on leaving dimensions in the EventApi, but only for client-side. + if (markToCleanup) { + markToCleanup = false; + cleanup(); // This will unset the isSetupComplete, causing a setup() call. + } //=================// // create the LODs // @@ -187,44 +213,32 @@ public class LodRenderer // 2. we aren't already regenerating the LODs // 3. we aren't waiting for the build and draw buffers to swap // (this is to prevent thread conflicts) - if ((partialRegen || fullRegen) && !lodBufferBuilderFactory.generatingBuffers && !lodBufferBuilderFactory.newBuffersAvailable()) - { - // generate the LODs on a separate thread to prevent stuttering or freezing - lodBufferBuilderFactory.generateLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), fullRegen); - + if (lodBufferBuilderFactory.updateAndSwapLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(), + MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), partialRegen, fullRegen)) { // the regen process has been started, - // it will be done when lodBufferBuilder.newBuffersAvailable() - // is true + // it will be done when lodBufferBuilder.newBuffersAvailable() is true fullRegen = false; partialRegen = false; } - // TODO move the buffer regeneration logic into its own class (probably called in the client api instead) - // ...ending here - - if (lodBufferBuilderFactory.newBuffersAvailable()) - { - swapBuffers(); - } + // Get the front buffers to draw + MovableGridList vbos = lodBufferBuilderFactory.getFrontBuffers(); + int vbosCenterX = lodBufferBuilderFactory.getFrontBuffersCenterX(); + int vbosCenterZ = lodBufferBuilderFactory.getFrontBuffersCenterZ(); + // @Unused if (vbos == null) { // There is still no vbos, which means nothing needs to be drawn. So no rendering needed // (Vbos should be setup by now) return; } - - // FIXME: Currently, we check for last Lod Dimension so that we can trigger a cleanup() if dimension has changed - // The better thing to do is to call cleanup() on leaving dimensions in the EventApi, but only for client-side. - if (markToCleanup) { - markToCleanup = false; - cleanup(); // This will unset the isSetupComplete, causing a setup() call. - } //===================// // draw params setup // //===================// profiler.push("LOD draw setup"); + LagSpikeCatcher drawSetup = new LagSpikeCatcher(); /*---------Set GL State--------*/ // Make sure to unbind current VBO so we don't mess up vanilla settings @@ -256,7 +270,7 @@ public class LodRenderer /*---------Get required data--------*/ // Get the matrixs for rendering - Mat4f modelViewMatrix = translateModelViewMatrix(mcModelViewMatrix, partialTicks); + Mat4f modelViewMatrix = translateModelViewMatrix(mcModelViewMatrix, partialTicks, vbosCenterX, vbosCenterZ); int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH; int farPlaneBlockDistance; // required for setupFog and setupProjectionMatrix @@ -269,7 +283,7 @@ public class LodRenderer /*---------Fill uniform data--------*/ // Fill the uniform data. Note: GL33.GL_TEXTURE0 == texture bindpoint 0 - shaderProgram.fillUniformData(modelViewMatrix, projectionMatrix, getTranslatedCameraPos(), + shaderProgram.fillUniformData(modelViewMatrix, projectionMatrix, getTranslatedCameraPos(vbosCenterX, vbosCenterZ), MC_RENDER.isFogStateInUnderWater() ? getUnderWaterFogColor(partialTicks) : getFogColor(partialTicks), (int) (MC.getSkyDarken(partialTicks) * 15), 0); // Previous guy said fog setting may be different from region to region, but the fogSettings never changed... soooooo... shaderProgram.fillUniformDataForFog(fogSettings, MC_RENDER.isFogStateInUnderWater()); @@ -279,45 +293,44 @@ public class LodRenderer //===========// // rendering // //===========// - + drawSetup.end("LodDrawSetup"); profiler.popPush("LOD draw"); + LagSpikeCatcher draw = new LagSpikeCatcher(); boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling(); - boolean usingBufferStorage = glProxy.getGpuUploadMethod() == GpuUploadMethod.BUFFER_STORAGE; // where the center of the buffers is (needed when culling regions) // render each of the buffers - for (int x = 0; x < vbos.length; x++) - { - for (int z = 0; z < vbos.length; z++) - { - //int tempX = LodUtil.convertLevelPos(x + vbosCenterX - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL , LodUtil.BLOCK_DETAIL_LEVEL); - //int tempY = LodUtil.convertLevelPos(z + vbosCenterZ - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL, LodUtil.BLOCK_DETAIL_LEVEL); + int lowRegionX = vbos.getCenterX() - vbos.gridCentreToEdge; + int lowRegionZ = vbos.getCenterY() - vbos.gridCentreToEdge; + int drawCall = 0; + for (int regionX=lowRegionX; regionX
- *

- * For some reason this has to be called after the frame has been rendered, - * otherwise visual stuttering/rubber banding may happen. I'm not sure why... - */ - private void swapBuffers() - { - // replace the drawable buffers with - // the newly created buffers from the lodBufferBuilder - VertexBuffersAndOffset result = lodBufferBuilderFactory.getVertexBuffers(); - vbos = result.vbos; - storageBufferIds = result.storageBufferIds; - vbosCenterX = result.drawableCenterBlockPosX; - vbosCenterZ = result.drawableCenterBlockPosZ; + // returns whether anything changed + private boolean updateVanillaRenderedChunks(LodDimension lodDim, boolean recreateChunks) { + short chunkRenderDistance = (short) MC_RENDER.getRenderDistance(); + int chunkX = Math.floorDiv(previousPos.getX(), 16); + int chunkZ = Math.floorDiv(previousPos.getZ(), 16); + // if the player is high enough, draw all LODs + if (previousPos.getY() > 256) { + vanillaRenderedChunks = new VanillaRenderedChunksList( + chunkRenderDistance, chunkX, chunkZ); + return true; + } + VanillaRenderedChunksList chunkList; + + if (recreateChunks) { + vanillaRenderedChunks = new VanillaRenderedChunksList(chunkRenderDistance, chunkX, chunkZ); + return true; + } else { + chunkList = vanillaRenderedChunks; + chunkX = chunkList.centerX; + chunkZ = chunkList.centerZ; + chunkRenderDistance = (short) vanillaRenderedChunks.gridCentreToEdge; + } + + boolean anyChanged = false; + LagSpikeCatcher getChunks = new LagSpikeCatcher(); + Set chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, previousPos); + getChunks.end("LodDrawSetup:UpdateStatus:UpdateVanillaChunks:getChunks"); + for (AbstractChunkPosWrapper pos : chunkPosToSkip) + { + int xIndex = (pos.getX() - chunkX) + (chunkRenderDistance + 1); + int zIndex = (pos.getZ() - chunkZ) + (chunkRenderDistance + 1); + + // sometimes we are given chunks that are outside the render distance, + // This prevents index out of bounds exceptions + if (xIndex >= 0 && zIndex >= 0 + && xIndex < vanillaRenderedChunks.gridSize + && zIndex < vanillaRenderedChunks.gridSize) + { + if (!chunkList.get(chunkList.calculateOffset(xIndex, zIndex))) + { + chunkList.set(chunkList.calculateOffset(xIndex, zIndex), true); + anyChanged = true; + lodDim.markRegionBufferToRegen(pos.getRegionX(), pos.getRegionZ()); + } + } + } + vanillaRenderedChunks = chunkList; + return anyChanged; } - /** Determines if the LODs should have a fullRegen or partialRegen */ - private void determineIfLodsShouldRegenerate(LodDimension lodDim, float partialTicks) - { + private void updateRegenStatus(LodDimension lodDim, float partialTicks) { short chunkRenderDistance = (short) MC_RENDER.getRenderDistance(); - int vanillaRenderedChunksWidth = chunkRenderDistance * 2 + 2; - - //=============// - // full regens // - //=============// - - // check if the view distance changed + long newTime = System.currentTimeMillis(); + AbstractBlockPosWrapper newPos = MC.getPlayerBlockPos(); + boolean shouldUpdateChunks = false; + boolean posUpdated = false; + boolean tryPartialGen = false; + boolean tryFullGen = false; + + // check if the view distance or config changed if (ApiShared.previousLodRenderDistance != CONFIG.client().graphics().quality().getLodChunkRenderDistance() || chunkRenderDistance != prevRenderDistance || prevFogDistance != CONFIG.client().graphics().fogQuality().getFogDistance()) { - - vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth]; - DetailDistanceUtil.updateSettings(); - fullRegen = true; - previousPos = LevelPosUtil.createLevelPos((byte) 4, MC.getPlayerChunkPos().getZ(), MC.getPlayerChunkPos().getZ()); + DetailDistanceUtil.updateSettings(); // FIXME: This should NOT be here! prevFogDistance = CONFIG.client().graphics().fogQuality().getFogDistance(); prevRenderDistance = chunkRenderDistance; - } - - // did the user change the debug setting? - if (CONFIG.client().advanced().debugging().getDebugMode() != previousDebugMode) - { + tryFullGen = true; + } else if (CONFIG.client().advanced().debugging().getDebugMode() != previousDebugMode) + { // did the user change the debug setting? previousDebugMode = CONFIG.client().advanced().debugging().getDebugMode(); - fullRegen = true; + tryFullGen = true; } - - long newTime = System.currentTimeMillis(); - // check if the player has moved - if (newTime - prevPlayerPosTime > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveTimeout) - { - if (LevelPosUtil.getDetailLevel(previousPos) == 0 - || Math.abs(MC.getPlayerChunkPos().getX() - LevelPosUtil.getPosX(previousPos)) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance - || Math.abs(MC.getPlayerChunkPos().getZ() - LevelPosUtil.getPosZ(previousPos)) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance) + if (newTime - prevPlayerPosTime > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveTimeout) { + if (previousPos == null + || Math.abs(newPos.getX() - previousPos.getX()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16 + || Math.abs(newPos.getZ() - previousPos.getZ()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16) { - vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth]; - fullRegen = true; - previousPos = LevelPosUtil.createLevelPos((byte) 4, MC.getPlayerChunkPos().getX(), MC.getPlayerChunkPos().getZ()); + tryPartialGen = true; + previousPos = newPos; + posUpdated = true; } prevPlayerPosTime = newTime; } - - //================// - // partial regens // - //================// - + // check if the vanilla rendered chunks changed if (newTime - prevVanillaChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().renderedChunkTimeout) { - if (vanillaRenderedChunksChanged) - { - partialRegen = true; - vanillaRenderedChunksChanged = false; - } + shouldUpdateChunks = true; prevVanillaChunkTime = newTime; } - + // check if there is any newly generated terrain to show if (newTime - prevChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().chunkChangeTimeout) { + tryPartialGen = true; + prevChunkTime = newTime; + } + + + if (tryFullGen && !posUpdated) { + previousPos = newPos; + posUpdated = true; + } + shouldUpdateChunks |= posUpdated; + if (shouldUpdateChunks) { + tryPartialGen |= updateVanillaRenderedChunks(lodDim, posUpdated); + } + + if (tryFullGen) { + fullRegen = true; + lodDim.regenDimensionBuffers = false; + } else if (tryPartialGen) { if (lodDim.regenDimensionBuffers) { partialRegen = true; lodDim.regenDimensionBuffers = false; } - prevChunkTime = newTime; } - - //==============// - // LOD skipping // - //==============// - - // determine which LODs should not be rendered close to the player - HashSet chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, MC.getPlayerBlockPos()); - int xIndex; - int zIndex; - for (AbstractChunkPosWrapper pos : chunkPosToSkip) - { - vanillaRenderedChunksEmptySkip = false; - - xIndex = (pos.getX() - MC.getPlayerChunkPos().getX()) + (chunkRenderDistance + 1); - zIndex = (pos.getZ() - MC.getPlayerChunkPos().getZ()) + (chunkRenderDistance + 1); - - // sometimes we are given chunks that are outside the render distance, - // This prevents index out of bounds exceptions - if (xIndex >= 0 && zIndex >= 0 - && xIndex < vanillaRenderedChunks.length - && zIndex < vanillaRenderedChunks.length) - { - if (!vanillaRenderedChunks[xIndex][zIndex]) - { - vanillaRenderedChunks[xIndex][zIndex] = true; - vanillaRenderedChunksChanged = true; - lodDim.markRegionBufferToRegen(pos.getRegionX(), pos.getRegionZ()); - } - } - } - - - // if the player is high enough, draw all LODs - if (chunkPosToSkip.isEmpty() && MC.getPlayerBlockPos().getY() > 256 && !vanillaRenderedChunksEmptySkip) - { - vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth]; - vanillaRenderedChunksChanged = true; - vanillaRenderedChunksEmptySkip = true; - } - - vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth]; } } diff --git a/src/main/java/com/seibel/lod/core/render/RenderUtil.java b/src/main/java/com/seibel/lod/core/render/RenderUtil.java index abb131123..5bac64cbf 100644 --- a/src/main/java/com/seibel/lod/core/render/RenderUtil.java +++ b/src/main/java/com/seibel/lod/core/render/RenderUtil.java @@ -85,23 +85,20 @@ public class RenderUtil * Returns true if one of the region's 4 corners is in front * of the camera. */ - public static boolean isRegionInViewFrustum(AbstractBlockPosWrapper playerBlockPos, Vec3f cameraDir, int vboCenterPosX, int vboCenterPosZ) + public static boolean isRegionInViewFrustum(AbstractBlockPosWrapper playerBlockPos, Vec3f cameraDir, int vboRegionX, int vboRegionZ) { // convert the vbo position into a direction vector // starting from the player's position - Vec3f vboVec = new Vec3f(vboCenterPosX, 0, vboCenterPosZ); + Vec3f vboVec = new Vec3f(vboRegionX * LodUtil.REGION_WIDTH, 0, vboRegionZ * LodUtil.REGION_WIDTH); Vec3f playerVec = new Vec3f(playerBlockPos.getX(), playerBlockPos.getY(), playerBlockPos.getZ()); vboVec.subtract(playerVec); - - int halfRegionWidth = LodUtil.REGION_WIDTH / 2; - // calculate the 4 corners - Vec3f vboSeVec = new Vec3f(vboVec.x + halfRegionWidth, vboVec.y, vboVec.z + halfRegionWidth); - Vec3f vboSwVec = new Vec3f(vboVec.x - halfRegionWidth, vboVec.y, vboVec.z + halfRegionWidth); - Vec3f vboNwVec = new Vec3f(vboVec.x - halfRegionWidth, vboVec.y, vboVec.z - halfRegionWidth); - Vec3f vboNeVec = new Vec3f(vboVec.x + halfRegionWidth, vboVec.y, vboVec.z - halfRegionWidth); + Vec3f vboSeVec = new Vec3f(vboVec.x + LodUtil.REGION_WIDTH, vboVec.y, vboVec.z + LodUtil.REGION_WIDTH); + Vec3f vboSwVec = new Vec3f(vboVec.x , vboVec.y, vboVec.z + LodUtil.REGION_WIDTH); + Vec3f vboNwVec = new Vec3f(vboVec.x , vboVec.y, vboVec.z); + Vec3f vboNeVec = new Vec3f(vboVec.x + LodUtil.REGION_WIDTH, vboVec.y, vboVec.z); // if any corner is visible, this region should be rendered return isNormalizedVectorInViewFrustum(vboSeVec, cameraDir) || diff --git a/src/main/java/com/seibel/lod/core/util/GridList.java b/src/main/java/com/seibel/lod/core/util/GridList.java new file mode 100644 index 000000000..8517aef2c --- /dev/null +++ b/src/main/java/com/seibel/lod/core/util/GridList.java @@ -0,0 +1,76 @@ +package com.seibel.lod.core.util; + +import java.util.ArrayList; +import java.util.List; + +public class GridList extends ArrayList implements List { + + public static class Pos { + public int x; + public int y; + + public Pos(int xx, int yy) { + x = xx; + y = yy; + } + } + + private static final long serialVersionUID = 1585978374811888116L; + public final int gridCentreToEdge; + public final int gridSize; + + public GridList(int gridCentreToEdge) { + super((gridCentreToEdge * 2 + 1) * (gridCentreToEdge * 2 + 1)); + gridSize = gridCentreToEdge * 2 + 1; + this.gridCentreToEdge = gridCentreToEdge; + } + + public final T getOffsetOf(int index, int x, int y) { + return get(index + x + y * gridSize); + } + + public final int offsetOf(int index, int x, int y) { + return index + x + y * gridSize; + } + + public final Pos posOf(int index) { + return new Pos(index % gridSize, index / gridSize); + } + + public final int calculateOffset(int x, int y) { + return x + y * gridSize; + } + + public final GridList subGrid(int gridCentreToEdge) { + int centreIndex = size() / 2; + GridList subGrid = new GridList(gridCentreToEdge); + for (int oy = -gridCentreToEdge; oy <= gridCentreToEdge; oy++) { + int begin = offsetOf(centreIndex, -gridCentreToEdge, oy); + int end = offsetOf(centreIndex, gridCentreToEdge, oy); + subGrid.addAll(this.subList(begin, end + 1)); + } + // System.out.println("========================================\n"+ + // this.toDetailString() + "\nTOOOOOOOOOOOOO\n"+subGrid.toDetailString()+ + // "==========================================\n"); + return subGrid; + } + + @Override + public String toString() { + return "GridList " + gridSize + "*" + gridSize + "[" + size() + "]"; + } + + public String toDetailString() { + StringBuilder str = new StringBuilder("\n"); + int i = 0; + for (T t : this) { + str.append(t.toString()); + str.append(", "); + i++; + if (i % gridSize == 0) { + str.append("\n"); + } + } + return str.toString(); + } +} diff --git a/src/main/java/com/seibel/lod/core/util/LodUtil.java b/src/main/java/com/seibel/lod/core/util/LodUtil.java index c13d78dcd..d14b41932 100644 --- a/src/main/java/com/seibel/lod/core/util/LodUtil.java +++ b/src/main/java/com/seibel/lod/core/util/LodUtil.java @@ -181,12 +181,6 @@ public class LodUtil return new RegionPos(relativePosX, relativePosZ); } - /** Convert a 2D absolute position into a quad tree relative position. */ - public static int convertLevelPos(int pos, int currentDetailLevel, int targetDetailLevel) - { - return pos / (1 << (targetDetailLevel - currentDetailLevel)); - } - /** * If on single player this will return the name of the user's diff --git a/src/main/java/com/seibel/lod/core/util/MovableGridList.java b/src/main/java/com/seibel/lod/core/util/MovableGridList.java new file mode 100644 index 000000000..7a2d75968 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/util/MovableGridList.java @@ -0,0 +1,168 @@ +package com.seibel.lod.core.util; + +import java.util.ArrayList; +import java.util.List; + +/*Layout: + * 0,1,2, + * 3,4,5, + * 6,7,8 + */ + +public class MovableGridList extends ArrayList implements List { + + private static final long serialVersionUID = 5366261085254591277L; + + public static class Pos { + public int x; + public int y; + + public Pos(int xx, int yy) { + x = xx; + y = yy; + } + } + + private int centerX; + private int centerY; + + public final int gridCentreToEdge; + public final int gridSize; + + public MovableGridList(int gridCentreToEdge, int centerX, int centerY) { + super((gridCentreToEdge * 2 + 1) * (gridCentreToEdge * 2 + 1)); + gridSize = gridCentreToEdge * 2 + 1; + this.gridCentreToEdge = gridCentreToEdge; + this.centerX = centerX; + this.centerY = centerY; + clear(); + } + + @Override + public void clear() { + super.clear(); + super.ensureCapacity(gridSize*gridSize); + for (int i=0; i=gridSize || y<0 || y>=gridSize) return null; + return super.get(x + y * gridSize); + } + private final boolean _setDirect(int x, int y, T t) { + if (x<0 || x>=gridSize || y<0 || y>=gridSize) return false; + super.set(x + y * gridSize, t); + return true; + } + + public void move(int newCenterX, int newCenterY) { + if (centerX == newCenterX && centerY == newCenterY) return; + int deltaX = newCenterX - centerX; + int deltaY = newCenterY - centerY; + + // if the x or z offset is equal to or greater than + // the total width, just delete the current data + // and update the centerX and/or centerZ + if (Math.abs(deltaX) >= gridSize || Math.abs(deltaY) >= gridSize) + { + clear(); + // update the new center + centerX = newCenterX; + centerY = newCenterY; + return; + } + + // X + if (deltaX >= 0 && deltaY >= 0) + { + // move everything over to the left-up (as the center moves to the right-down) + for (int x = 0; x < gridSize; x++) + { + for (int y = 0; y < gridSize; y++) + { + _setDirect(x, y, _getDirect(x+deltaX, y+deltaY)); + } + } + } + else if (deltaX < 0 && deltaY >= 0) + { + // move everything over to the right-up (as the center moves to the left-down) + for (int x = gridSize - 1; x >= 0; x--) + { + for (int y = 0; y < gridSize; y++) + { + _setDirect(x, y, _getDirect(x+deltaX, y+deltaY)); + } + } + } + else if (deltaX >= 0 && deltaY < 0) + { + // move everything over to the left-down (as the center moves to the right-up) + for (int x = 0; x < gridSize; x++) + { + for (int y = gridSize - 1; y >= 0; y--) + { + _setDirect(x, y, _getDirect(x+deltaX, y+deltaY)); + } + } + } + else //if (deltaX < 0 && deltaY < 0) + { + // move everything over to the right-down (as the center moves to the left-up) + for (int x = gridSize - 1; x >= 0; x--) + { + for (int y = gridSize - 1; y >= 0; y--) + { + _setDirect(x, y, _getDirect(x+deltaX, y+deltaY)); + } + } + } + centerX = newCenterX; + centerY = newCenterY; + } + + + // TODO: This is unused but may be useful later on. + /* + public final MovableGridList subGrid(int gridCentreToEdge, int newCenterX, int newCenterY) { + }*/ + + @Override + public String toString() { + return "MovableGridList[" + centerX + "," + centerY + "] " + gridSize + "*" + gridSize + "[" + size() + "]"; + } + + public String toDetailString() { + StringBuilder str = new StringBuilder("\n"); + int i = 0; + for (T t : this) { + + str.append(t!=null ? t.toString() : "NULL"); + str.append(", "); + i++; + if (i % gridSize == 0) { + str.append("\n"); + } + } + return str.toString(); + } +}