diff --git a/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java b/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java index 6d81b3718..c369aa493 100644 --- a/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java +++ b/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java @@ -26,7 +26,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; -import com.seibel.lod.util.DetailUtil; import org.lwjgl.opengl.GL11; import com.seibel.lod.builders.worldGeneration.LodNodeGenWorker; @@ -39,6 +38,7 @@ import com.seibel.lod.objects.LodDimension; import com.seibel.lod.objects.RegionPos; import com.seibel.lod.proxy.ClientProxy; import com.seibel.lod.render.LodRenderer; +import com.seibel.lod.util.DetailUtil; import com.seibel.lod.util.LodThreadFactory; import com.seibel.lod.util.LodUtil; @@ -58,206 +58,213 @@ import net.minecraftforge.common.WorldWorkerManager; */ public class LodBufferBuilder { - private Minecraft mc; - - /** - * This holds the thread used to generate new LODs off the main thread. - */ - private ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " - main")); - - private LodBuilder LodQuadTreeNodeBuilder; - - /** - * The buffers that are used to create LODs using far fog - */ - public volatile BufferBuilder[][] buildableBuffers; - - /** - * Used when building new VBOs - */ - public volatile VertexBuffer[][] buildableVbos; - - /** - * VBOs that are sent over to the LodNodeRenderer - */ - public volatile VertexBuffer[][] drawableVbos; - - /** - * if this is true the LOD buffers are currently being - * regenerated. - */ - public boolean generatingBuffers = false; - - /** - * if this is true the LOD buffers are currently being - * regenerated. - */ - public Set positionWaitingToBeGenerated = new HashSet<>(); - - /** - * if this is true new LOD buffers have been generated - * and are waiting to be swapped with the drawable buffers - */ - private boolean switchVbos = false; - - /** - * This keeps track of how many chunk generation requests are on going. - * This is to prevent chunks from being generated for a long time in an area - * the player is no longer in. - */ - public AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0); - - - /** - * how many chunks to generate outside of the player's - * view distance at one time. (or more specifically how - * many requests to make at one time). - * I multiply by 8 to make sure there is always a buffer of chunk requests, - * to make sure the CPU is always busy and we can generate LODs as quickly as - * possible. - */ - public int maxChunkGenRequests = LodConfig.CLIENT.numberOfWorldGenerationThreads.get() * 8; - - - public LodBufferBuilder(LodBuilder newLodBuilder) - { - mc = Minecraft.getInstance(); - LodQuadTreeNodeBuilder = newLodBuilder; - } - - - private LodDimension previousDimension = null; - - - /** - * Create a thread to asynchronously generate LOD buffers - * centered around the given camera X and Z. - *
- * This method will write to the drawable near and far buffers. - *
- * After the buildable buffers have been generated they must be - * swapped with the drawable buffers in the LodRenderer to be drawn. - */ - public void generateLodBuffersAsync(LodRenderer renderer, LodDimension lodDim, - BlockPos playerBlockPos, int numbChunksWide) - { - // only allow one generation process to happen at a time - if (generatingBuffers) - return; - - if (buildableBuffers == null) - throw new IllegalStateException("\"generateLodBuffersAsync\" was called before the \"setupBuffers\" method was called."); - - if (previousDimension != lodDim) - { - previousDimension = lodDim; - } - - - generatingBuffers = true; - - - // round the player's block position down to the nearest chunk BlockPos - ChunkPos playerChunkPos = new ChunkPos(playerBlockPos); - BlockPos playerBlockPosRounded = playerChunkPos.getWorldPosition(); - - // this is where we will start drawing squares - // (exactly half the total width) - BlockPos startBlockPos = new BlockPos(-(numbChunksWide * 16 / 2) + playerBlockPosRounded.getX(), 0, -(numbChunksWide * 16 / 2) + playerBlockPosRounded.getZ()); - ChunkPos startChunkPos = new ChunkPos(startBlockPos); - - - Thread thread = new Thread(() -> - { - try - { - long startTime = System.currentTimeMillis(); - - ArrayList chunksToGen = new ArrayList<>(maxChunkGenRequests); - // if we don't have a full number of chunks to generate in chunksToGen - // we can top it off from the reserve - ArrayList chunksToGenReserve = new ArrayList<>(maxChunkGenRequests); - - startBuffers(); - - // used when determining which chunks are closer when queuing distance - // generation - int minChunkDist = Integer.MAX_VALUE; - int width; - - // =====================// - // RENDERING PART // - // =====================// - - List posListToRender = new ArrayList<>(); - LodDataPoint lodData; - for (int xRegion = 0; xRegion < lodDim.regions.length; xRegion++) - { - for (int zRegion = 0; zRegion < lodDim.regions.length; zRegion++) - { - RegionPos regionPos = new RegionPos(xRegion + lodDim.getCenterX() - lodDim.getWidth() / 2, zRegion + lodDim.getCenterZ() - lodDim.getWidth() / 2); - - // local position in the vbo and bufferBuilder arrays - BufferBuilder currentBuffer = buildableBuffers[xRegion][zRegion]; - - // make sure the buffers weren't - // changed while we were running this method - if (currentBuffer == null || (currentBuffer != null && !currentBuffer.building())) - return; - - /**TODO make this automatic and config dependent*/ - for (byte detail = LodUtil.BLOCK_DETAIL_LEVEL; detail <= LodUtil.REGION_DETAIL_LEVEL; detail++) - { - posListToRender.addAll(lodDim.getDataToRender( - regionPos, - playerBlockPosRounded.getX(), - playerBlockPosRounded.getZ(), - DetailUtil.getDistanceRendering(detail), - DetailUtil.getDistanceRendering(detail + 1), - detail)); - } - for (LevelPos pos : posListToRender) - { - LevelPos chunkPos = pos.convert(LodUtil.CHUNK_DETAIL_LEVEL); - // skip any chunks that Minecraft is going to render - - if (renderer.vanillaRenderedChunks.contains(new ChunkPos( chunkPos.posX, chunkPos.posZ))) - { - continue; - } - - if (lodDim.doesDataExist(pos)) - { - try - { - width = (int) Math.pow(2, pos.detailLevel); - lodData = lodDim.getData(pos); - LodConfig.CLIENT.lodTemplate.get().template.addLodToBuffer(currentBuffer, lodDim, lodData, - pos.posX * width, 0, pos.posZ * width, renderer.debugging, pos.detailLevel); - } catch (ArrayIndexOutOfBoundsException e) - { - ClientProxy.LOGGER.warn("LodBufferBuilder ran into trouble and had to start over."); - closeBuffers(); - return; - } - } - - } - - posListToRender.clear(); - } - } - - - // =====================// - // GENERATION PART // - // =====================// - - - List posListToGenerate = new ArrayList<>(); - - /**TODO can give a totally different generation*/ - /* + private Minecraft mc; + + /** + * This holds the thread used to generate new LODs off the main thread. + */ + private ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " - main")); + + private LodBuilder LodQuadTreeNodeBuilder; + + /** + * The buffers that are used to create LODs using far fog + */ + public volatile BufferBuilder[][] buildableBuffers; + + /** + * Used when building new VBOs + */ + public volatile VertexBuffer[][] buildableVbos; + + /** + * VBOs that are sent over to the LodNodeRenderer + */ + public volatile VertexBuffer[][] drawableVbos; + + /** + * if this is true the LOD buffers are currently being + * regenerated. + */ + public boolean generatingBuffers = false; + + /** + * if this is true the LOD buffers are currently being + * regenerated. + */ + public Set positionWaitingToBeGenerated = new HashSet<>(); + + /** + * if this is true new LOD buffers have been generated + * and are waiting to be swapped with the drawable buffers + */ + private boolean switchVbos = false; + + /** + * This keeps track of how many chunk generation requests are on going. + * This is to prevent chunks from being generated for a long time in an area + * the player is no longer in. + */ + public AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0); + + + /** + * how many chunks to generate outside of the player's + * view distance at one time. (or more specifically how + * many requests to make at one time). + * I multiply by 8 to make sure there is always a buffer of chunk requests, + * to make sure the CPU is always busy and we can generate LODs as quickly as + * possible. + */ + public int maxChunkGenRequests = LodConfig.CLIENT.numberOfWorldGenerationThreads.get() * 8; + + /** Size of the buffer builders in bytes last time we created them */ + public int previousBufferSize = 0; + + /** Width of the dimension in regions last time we created the buffers */ + public int previousRegionWidth = 0; + + + + public LodBufferBuilder(LodBuilder newLodBuilder) + { + mc = Minecraft.getInstance(); + LodQuadTreeNodeBuilder = newLodBuilder; + } + + + private LodDimension previousDimension = null; + + + /** + * Create a thread to asynchronously generate LOD buffers + * centered around the given camera X and Z. + *
+ * This method will write to the drawable near and far buffers. + *
+ * After the buildable buffers have been generated they must be + * swapped with the drawable buffers in the LodRenderer to be drawn. + */ + public void generateLodBuffersAsync(LodRenderer renderer, LodDimension lodDim, + BlockPos playerBlockPos, int numbChunksWide) + { + // only allow one generation process to happen at a time + if (generatingBuffers) + return; + + if (buildableBuffers == null) + throw new IllegalStateException("\"generateLodBuffersAsync\" was called before the \"setupBuffers\" method was called."); + + if (previousDimension != lodDim) + { + previousDimension = lodDim; + } + + + generatingBuffers = true; + + + // round the player's block position down to the nearest chunk BlockPos + ChunkPos playerChunkPos = new ChunkPos(playerBlockPos); + BlockPos playerBlockPosRounded = playerChunkPos.getWorldPosition(); + + + Thread thread = new Thread(() -> + { + try + { + long startTime = System.currentTimeMillis(); + + ArrayList chunksToGen = new ArrayList<>(maxChunkGenRequests); + // if we don't have a full number of chunks to generate in chunksToGen + // we can top it off from the reserve + ArrayList chunksToGenReserve = new ArrayList<>(maxChunkGenRequests); + + startBuffers(); + + // used when determining which chunks are closer when queuing distance + // generation + int minChunkDist = Integer.MAX_VALUE; + int width; + + // =====================// + // RENDERING PART // + // =====================// + + List posListToRender = new ArrayList<>(); + LodDataPoint lodData; + for (int xRegion = 0; xRegion < lodDim.regions.length; xRegion++) + { + for (int zRegion = 0; zRegion < lodDim.regions.length; zRegion++) + { + RegionPos regionPos = new RegionPos(xRegion + lodDim.getCenterX() - lodDim.getWidth() / 2, zRegion + lodDim.getCenterZ() - lodDim.getWidth() / 2); + + // local position in the vbo and bufferBuilder arrays + BufferBuilder currentBuffer = buildableBuffers[xRegion][zRegion]; + + // make sure the buffers weren't + // changed while we were running this method + if (currentBuffer == null || (currentBuffer != null && !currentBuffer.building())) + return; + + /**TODO make this automatic and config dependent*/ + for (byte detail = LodUtil.BLOCK_DETAIL_LEVEL; detail <= LodUtil.REGION_DETAIL_LEVEL; detail++) + { + posListToRender.addAll(lodDim.getDataToRender( + regionPos, + playerBlockPosRounded.getX(), + playerBlockPosRounded.getZ(), + DetailUtil.getDistanceRendering(detail), + DetailUtil.getDistanceRendering(detail + 1), + detail)); + } + for (LevelPos pos : posListToRender) + { + LevelPos chunkPos = pos.convert(LodUtil.CHUNK_DETAIL_LEVEL); + // skip any chunks that Minecraft is going to render + + if (renderer.vanillaRenderedChunks.contains(new ChunkPos( chunkPos.posX, chunkPos.posZ))) + { + continue; + } + + if (lodDim.doesDataExist(pos)) + { + try + { + width = (int) Math.pow(2, pos.detailLevel); + lodData = lodDim.getData(pos); + + if (lodData != null) + { + LodConfig.CLIENT.lodTemplate.get().template.addLodToBuffer(currentBuffer, lodDim, lodData, + pos.posX * width, 0, pos.posZ * width, renderer.debugging, pos.detailLevel); + } + } + catch (ArrayIndexOutOfBoundsException e) + { + ClientProxy.LOGGER.warn("LodBufferBuilder ran into trouble and had to start over."); + closeBuffers(); + return; + } + } + + } + + posListToRender.clear(); + } + } + + + // =====================// + // GENERATION PART // + // =====================// + + + List posListToGenerate = new ArrayList<>(); + + /**TODO can give a totally different generation*/ + /* for (byte detail = LodUtil.BLOCK_DETAIL_LEVEL; detail <= LodUtil.REGION_DETAIL_LEVEL; detail++) { if (!posListToGenerate.isEmpty()) break; @@ -275,289 +282,300 @@ public class LodBufferBuilder System.out.println("HERE"); } } - */ - int requesting = maxChunkGenRequests; - - //we firstly make sure that the world is filled with half region wide block - for (byte detailGen = LodUtil.BLOCK_DETAIL_LEVEL; detailGen <= LodUtil.REGION_DETAIL_LEVEL; detailGen++) - { - if (requesting == 0) break; - posListToGenerate.addAll(lodDim.getDataToGenerate( - playerBlockPosRounded.getX(), - playerBlockPosRounded.getZ(), - DetailUtil.getDistanceGeneration(detailGen), - DetailUtil.getDistanceGeneration(detailGen + 1), - DetailUtil.getDistanceGenerationMode(detailGen).complexity, - (byte) 7, - requesting)); - requesting = maxChunkGenRequests - posListToGenerate.size(); - - for (LevelPos levelPos : posListToGenerate) - { - } - - } - - //we then fill the world with the rest of the block - for (byte detailGen = LodUtil.BLOCK_DETAIL_LEVEL; detailGen <= LodUtil.REGION_DETAIL_LEVEL; detailGen++) - { - if (requesting == 0) break; - posListToGenerate.addAll(lodDim.getDataToGenerate( - playerBlockPosRounded.getX(), - playerBlockPosRounded.getZ(), - DetailUtil.getDistanceGeneration(detailGen), - DetailUtil.getDistanceGeneration(detailGen + 1), - DetailUtil.getDistanceGenerationMode(detailGen).complexity, - (byte) 0, - maxChunkGenRequests)); - requesting = maxChunkGenRequests - posListToGenerate.size(); - } - - if (LodConfig.CLIENT.distanceGenerationMode.get() != DistanceGenerationMode.NONE) - { - // determine which points in the posListToGenerate - // should actually be queued up - for (LevelPos levelPos : posListToGenerate) - { - LevelPos chunkLevelPos = levelPos.convert(LodUtil.CHUNK_DETAIL_LEVEL); - int chunkX = chunkLevelPos.posX; - int chunkZ = chunkLevelPos.posZ; - - if (numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests) - { - ChunkPos pos = new ChunkPos(chunkX, chunkZ); - - if (positionWaitingToBeGenerated.contains(pos)) - { - //ClientProxy.LOGGER.debug(pos + " asked to be generated again."); - continue; - } - - // determine if this position is closer to the player - // than the previous - int newDistance = playerChunkPos.getChessboardDistance(pos); - - if (newDistance < minChunkDist) - { - // this chunk is closer, clear any previous - // positions and update the new minimum distance - minChunkDist = newDistance; - - // move all the old chunks into the reserve - ArrayList oldReserve = new ArrayList<>(chunksToGenReserve); - chunksToGenReserve.clear(); - chunksToGenReserve.addAll(chunksToGen); - // top off reserve with whatever was in oldReerve - for (int i = 0; i < oldReserve.size(); i++) - { - if (chunksToGenReserve.size() < maxChunkGenRequests) - chunksToGenReserve.add(oldReserve.get(i)); - else - break; - } - - chunksToGen.clear(); - chunksToGen.add(pos); - } else if (newDistance == minChunkDist) - { - // this chunk position as close as the minimum distance - if (chunksToGen.size() < maxChunkGenRequests) - { - // we are still under the number of chunks to generate - // add this position to the list - chunksToGen.add(pos); - } - } else - { - // this chunk is farther away than the minimum distance, - // add it to the reserve to make sure we always have a full reserve - chunksToGenReserve.add(pos); - } - - } // lod null and can generate more chunks - } // positions to generate - - - // queue up chunks to be generated - if (mc.hasSingleplayerServer()) - { - // issue #19 - // TODO add a way for a server side mod to generate chunks requested here - ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension); - - // make sure we have as many chunks to generate as we are allowed - if (chunksToGen.size() < maxChunkGenRequests) - { - Iterator reserveIterator = chunksToGenReserve.iterator(); - while (chunksToGen.size() < maxChunkGenRequests && reserveIterator.hasNext()) - { - chunksToGen.add(reserveIterator.next()); - } - } - - // start chunk generation - for (ChunkPos chunkPos : chunksToGen) - { - // don't add null chunkPos (which shouldn't happen anyway) - // or add more to the generation queue - if (chunkPos == null || numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests) - continue; - - positionWaitingToBeGenerated.add(chunkPos); - numberOfChunksWaitingToGenerate.addAndGet(1); - LodNodeGenWorker genWorker = new LodNodeGenWorker(chunkPos, DistanceGenerationMode.SURFACE, LodDetail.FULL, renderer, LodQuadTreeNodeBuilder, this, lodDim, serverWorld); - WorldWorkerManager.addWorker(genWorker); - } - } - } // if distanceGenerationMode != DistanceGenerationMode.NONE - - - // finish the buffer building - closeBuffers(); - - // upload the new buffers - uploadBuffers(); - - - long endTime = System.currentTimeMillis(); - long buildTime = endTime - startTime; - //ClientProxy.LOGGER.info("Buffer Build time: " + buildTime + " ms"); - - // mark that the buildable buffers as ready to swap - switchVbos = true; - } catch (Exception e) - { - ClientProxy.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" ran into trouble: "); - e.printStackTrace(); - } finally - { - // regardless of if we successfully created the buffers - // we are done generating. - generatingBuffers = false; - - - // clean up any potentially open resources - if (buildableBuffers != null) - closeBuffers(); - } - - }); - - mainGenThread.execute(thread); - - return; - } - - - //====================// - // generation helpers // - //====================// - - /** - * Returns if the given coordinate is in the loaded area of the world. - * - * @param centerCoordinate the center of the loaded world - */ - private boolean isCoordInCenterArea(int i, int j, int centerCoordinate) - { - return (i >= centerCoordinate - (mc.options.renderDistance + 2) - && i <= centerCoordinate + (mc.options.renderDistance + 2)) - && - (j >= centerCoordinate - (mc.options.renderDistance + 2) - && j <= centerCoordinate + (mc.options.renderDistance + 2)); - } - - - //===============================// - // BufferBuilder related methods // - //===============================// - - - /** - * Called from the LodRenderer to create the - * BufferBuilders. - */ - public void setupBuffers(int numbRegionsWide, int bufferMaxCapacity) - { - buildableBuffers = new BufferBuilder[numbRegionsWide][numbRegionsWide]; - - buildableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide]; - drawableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide]; - - for (int x = 0; x < numbRegionsWide; x++) - { - for (int z = 0; z < numbRegionsWide; z++) - { - buildableBuffers[x][z] = new BufferBuilder(bufferMaxCapacity); - buildableVbos[x][z] = new VertexBuffer(LodRenderer.LOD_VERTEX_FORMAT); - drawableVbos[x][z] = new VertexBuffer(LodRenderer.LOD_VERTEX_FORMAT); - } - } - } - - /** - * Calls begin on each of the buildable BufferBuilders. - */ - public void startBuffers() - { - for (int x = 0; x < buildableBuffers.length; x++) - for (int z = 0; z < buildableBuffers.length; z++) - buildableBuffers[x][z].begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT); - } - - /** - * Calls end on each of the buildable BufferBuilders. - */ - public void closeBuffers() - { - for (int x = 0; x < buildableBuffers.length; x++) - for (int z = 0; z < buildableBuffers.length; z++) - if (buildableBuffers[x][z] != null && buildableBuffers[x][z].building()) - buildableBuffers[x][z].end(); - } - - /** - * Called from the LodRenderer to create the - * BufferBuilders at the right size. - * - * @param bufferMaxCapacity - */ - public void uploadBuffers() - { - for (int x = 0; x < buildableVbos.length; x++) - { - for (int z = 0; z < buildableVbos.length; z++) - { - buildableVbos[x][z].upload(buildableBuffers[x][z]); - } - } - } - - - /** - * Get the newly created VBOs - */ - public VertexBuffer[][] getVertexBuffers() - { - VertexBuffer[][] tmp = drawableVbos; - drawableVbos = buildableVbos; - buildableVbos = tmp; - - // the vbos have been swapped - switchVbos = false; - - return drawableVbos; - } - - /** - * If this is true the buildable near and far - * buffers have been generated and are ready to be - * sent to the LodRenderer. - */ - public boolean newBuffersAvaliable() - { - return switchVbos; - } - - + */ + int requesting = maxChunkGenRequests; + + //we firstly make sure that the world is filled with half region wide block + for (byte detailGen = LodUtil.BLOCK_DETAIL_LEVEL; detailGen <= LodUtil.REGION_DETAIL_LEVEL; detailGen++) + { + if (requesting == 0) break; + posListToGenerate.addAll(lodDim.getDataToGenerate( + playerBlockPosRounded.getX(), + playerBlockPosRounded.getZ(), + DetailUtil.getDistanceGeneration(detailGen), + DetailUtil.getDistanceGeneration(detailGen + 1), + DetailUtil.getDistanceGenerationMode(detailGen).complexity, + (byte) 7, + requesting)); + requesting = maxChunkGenRequests - posListToGenerate.size(); + } + + //we then fill the world with the rest of the block + for (byte detailGen = LodUtil.BLOCK_DETAIL_LEVEL; detailGen <= LodUtil.REGION_DETAIL_LEVEL; detailGen++) + { + if (requesting == 0) break; + posListToGenerate.addAll(lodDim.getDataToGenerate( + playerBlockPosRounded.getX(), + playerBlockPosRounded.getZ(), + DetailUtil.getDistanceGeneration(detailGen), + DetailUtil.getDistanceGeneration(detailGen + 1), + DetailUtil.getDistanceGenerationMode(detailGen).complexity, + (byte) 0, + maxChunkGenRequests)); + requesting = maxChunkGenRequests - posListToGenerate.size(); + } + + if (LodConfig.CLIENT.distanceGenerationMode.get() != DistanceGenerationMode.NONE) + { + // determine which points in the posListToGenerate + // should actually be queued up + for (LevelPos levelPos : posListToGenerate) + { + LevelPos chunkLevelPos = levelPos.convert(LodUtil.CHUNK_DETAIL_LEVEL); + int chunkX = chunkLevelPos.posX; + int chunkZ = chunkLevelPos.posZ; + + if (numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests) + { + ChunkPos pos = new ChunkPos(chunkX, chunkZ); + + if (positionWaitingToBeGenerated.contains(pos)) + { + //ClientProxy.LOGGER.debug(pos + " asked to be generated again."); + continue; + } + + // determine if this position is closer to the player + // than the previous + int newDistance = playerChunkPos.getChessboardDistance(pos); + + if (newDistance < minChunkDist) + { + // this chunk is closer, clear any previous + // positions and update the new minimum distance + minChunkDist = newDistance; + + // move all the old chunks into the reserve + ArrayList oldReserve = new ArrayList<>(chunksToGenReserve); + chunksToGenReserve.clear(); + chunksToGenReserve.addAll(chunksToGen); + // top off reserve with whatever was in oldReerve + for (int i = 0; i < oldReserve.size(); i++) + { + if (chunksToGenReserve.size() < maxChunkGenRequests) + chunksToGenReserve.add(oldReserve.get(i)); + else + break; + } + + chunksToGen.clear(); + chunksToGen.add(pos); + } else if (newDistance == minChunkDist) + { + // this chunk position as close as the minimum distance + if (chunksToGen.size() < maxChunkGenRequests) + { + // we are still under the number of chunks to generate + // add this position to the list + chunksToGen.add(pos); + } + } else + { + // this chunk is farther away than the minimum distance, + // add it to the reserve to make sure we always have a full reserve + chunksToGenReserve.add(pos); + } + + } // lod null and can generate more chunks + } // positions to generate + + + // queue up chunks to be generated + if (mc.hasSingleplayerServer()) + { + // issue #19 + // TODO add a way for a server side mod to generate chunks requested here + ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension); + + // make sure we have as many chunks to generate as we are allowed + if (chunksToGen.size() < maxChunkGenRequests) + { + Iterator reserveIterator = chunksToGenReserve.iterator(); + while (chunksToGen.size() < maxChunkGenRequests && reserveIterator.hasNext()) + { + chunksToGen.add(reserveIterator.next()); + } + } + + // start chunk generation + for (ChunkPos chunkPos : chunksToGen) + { + // don't add null chunkPos (which shouldn't happen anyway) + // or add more to the generation queue + if (chunkPos == null || numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests) + continue; + + positionWaitingToBeGenerated.add(chunkPos); + numberOfChunksWaitingToGenerate.addAndGet(1); + LodNodeGenWorker genWorker = new LodNodeGenWorker(chunkPos, DistanceGenerationMode.SURFACE, LodDetail.FULL, renderer, LodQuadTreeNodeBuilder, this, lodDim, serverWorld); + WorldWorkerManager.addWorker(genWorker); + } + } + } // if distanceGenerationMode != DistanceGenerationMode.NONE + + + // finish the buffer building + closeBuffers(); + + // upload the new buffers + uploadBuffers(); + + + long endTime = System.currentTimeMillis(); + long buildTime = endTime - startTime; + //ClientProxy.LOGGER.info("Buffer Build time: " + buildTime + " ms"); + + // mark that the buildable buffers as ready to swap + switchVbos = true; + } catch (Exception e) + { + ClientProxy.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" ran into trouble: "); + e.printStackTrace(); + } finally + { + // regardless of if we successfully created the buffers + // we are done generating. + generatingBuffers = false; + + + // clean up any potentially open resources + if (buildableBuffers != null) + closeBuffers(); + } + + }); + + mainGenThread.execute(thread); + + return; + } + + + //====================// + // generation helpers // + //====================// + + /** + * Returns if the given coordinate is in the loaded area of the world. + * + * @param centerCoordinate the center of the loaded world + */ + private boolean isCoordInCenterArea(int i, int j, int centerCoordinate) + { + return (i >= centerCoordinate - (mc.options.renderDistance + 2) + && i <= centerCoordinate + (mc.options.renderDistance + 2)) + && + (j >= centerCoordinate - (mc.options.renderDistance + 2) + && j <= centerCoordinate + (mc.options.renderDistance + 2)); + } + + + //===============================// + // BufferBuilder related methods // + //===============================// + + + /** + * Called from the LodRenderer to create the + * BufferBuilders. + */ + public void setupBuffers(int numbRegionsWide, int bufferMaxCapacity) + { + previousRegionWidth = numbRegionsWide; + previousBufferSize = bufferMaxCapacity; + + + buildableBuffers = new BufferBuilder[numbRegionsWide][numbRegionsWide]; + + buildableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide]; + drawableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide]; + + for (int x = 0; x < numbRegionsWide; x++) + { + for (int z = 0; z < numbRegionsWide; z++) + { + buildableBuffers[x][z] = new BufferBuilder(bufferMaxCapacity); + buildableVbos[x][z] = new VertexBuffer(LodRenderer.LOD_VERTEX_FORMAT); + drawableVbos[x][z] = new VertexBuffer(LodRenderer.LOD_VERTEX_FORMAT); + } + } + } + + /** + * sets the buffers and Vbos to null, forcing them to be recreated. + */ + public void destroyBuffers() + { + buildableBuffers = null; + + buildableVbos = null; + drawableVbos = null; + } + + + /** + * Calls begin on each of the buildable BufferBuilders. + */ + public void startBuffers() + { + for (int x = 0; x < buildableBuffers.length; x++) + for (int z = 0; z < buildableBuffers.length; z++) + buildableBuffers[x][z].begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT); + } + + /** + * Calls end on each of the buildable BufferBuilders. + */ + public void closeBuffers() + { + for (int x = 0; x < buildableBuffers.length; x++) + for (int z = 0; z < buildableBuffers.length; z++) + if (buildableBuffers[x][z] != null && buildableBuffers[x][z].building()) + buildableBuffers[x][z].end(); + } + + /** + * Called from the LodRenderer to create the + * BufferBuilders at the right size. + * + * @param bufferMaxCapacity + */ + public void uploadBuffers() + { + for (int x = 0; x < buildableVbos.length; x++) + { + for (int z = 0; z < buildableVbos.length; z++) + { + buildableVbos[x][z].upload(buildableBuffers[x][z]); + } + } + } + + + /** + * Get the newly created VBOs + */ + public VertexBuffer[][] getVertexBuffers() + { + VertexBuffer[][] tmp = drawableVbos; + drawableVbos = buildableVbos; + buildableVbos = tmp; + + // the vbos have been swapped + switchVbos = false; + + return drawableVbos; + } + + /** + * If this is true the buildable near and far + * buffers have been generated and are ready to be + * sent to the LodRenderer. + */ + public boolean newBuffersAvaliable() + { + return switchVbos; + } + + } \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/objects/LodDimension.java b/src/main/java/com/seibel/lod/objects/LodDimension.java index 2164f9dad..1919811df 100644 --- a/src/main/java/com/seibel/lod/objects/LodDimension.java +++ b/src/main/java/com/seibel/lod/objects/LodDimension.java @@ -27,6 +27,7 @@ import java.util.stream.Collectors; import com.seibel.lod.enums.DistanceGenerationMode; import com.seibel.lod.handlers.LodDimensionFileHandler; +import com.seibel.lod.proxy.ClientProxy; import com.seibel.lod.util.LodUtil; import net.minecraft.client.Minecraft; @@ -438,14 +439,22 @@ public class LodDimension */ public List getDataToRender(RegionPos regionPos, int playerPosX, int playerPosZ, int start, int end, byte detailLevel) { - int n = regions.length; List listOfData = new ArrayList<>(); LodRegion region = getRegion(regionPos); if (region == null) { - region = new LodRegion((byte) 0, regionPos); - addOrOverwriteRegion(region); - } else + try + { + region = new LodRegion((byte) 0, regionPos); + addOrOverwriteRegion(region); + } + catch (ArrayIndexOutOfBoundsException e) + { + ClientProxy.LOGGER.warn("getDataToRender was unable to add the region at the pos [" + regionPos.x + ", " + regionPos.z + "]"); + return listOfData; // this list should be empty + } + } + else { listOfData.addAll(region.getDataToRender(playerPosX, playerPosZ, start, end, detailLevel)); } diff --git a/src/main/java/com/seibel/lod/proxy/ClientProxy.java b/src/main/java/com/seibel/lod/proxy/ClientProxy.java index b5d7e4d3d..1ed11b3d9 100644 --- a/src/main/java/com/seibel/lod/proxy/ClientProxy.java +++ b/src/main/java/com/seibel/lod/proxy/ClientProxy.java @@ -17,13 +17,18 @@ */ package com.seibel.lod.proxy; -import com.seibel.lod.enums.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.seibel.lod.builders.LodBufferBuilder; import com.seibel.lod.builders.LodBuilder; import com.seibel.lod.builders.worldGeneration.LodNodeGenWorker; +import com.seibel.lod.enums.DistanceCalculatorType; +import com.seibel.lod.enums.DistanceGenerationMode; +import com.seibel.lod.enums.FogDistance; +import com.seibel.lod.enums.FogDrawOverride; +import com.seibel.lod.enums.LodDetail; +import com.seibel.lod.enums.ShadingMode; import com.seibel.lod.handlers.LodConfig; import com.seibel.lod.objects.LodDimension; import com.seibel.lod.objects.LodWorld; @@ -48,239 +53,246 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; */ public class ClientProxy { - public static final Logger LOGGER = LogManager.getLogger("LOD"); - - private static LodWorld lodWorld = new LodWorld(); - private static LodBuilder lodBuilder = new LodBuilder(); - private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder(lodBuilder); - private static LodRenderer renderer = new LodRenderer(lodBufferBuilder); - - private boolean configOverrideReminderPrinted = false; - - Minecraft mc = Minecraft.getInstance(); - - /** - * This is used to determine if the LODs should be regenerated - */ - public static int previousChunkRenderDistance = 0; - /** - * This is used to determine if the LODs should be regenerated - */ - public static int previousLodMultiplierDistance = 0; - - - public ClientProxy() - { - - } - - - //==============// - // render event // - //==============// - - /** - * Do any setup that is required to draw LODs - * and then tell the LodRenderer to draw. - */ - public void renderLods(float partialTicks) - { - if (mc == null || mc.player == null || !lodWorld.getIsWorldLoaded()) - return; - - - viewDistanceChangedEvent(); - - LodDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType()); - if (lodDim == null) - return; - - playerMoveEvent(lodDim); - - - // comment out when creating a release - applyConfigOverrides(); - - - // Note to self: - // if "unspecified" shows up in the pie chart, it is - // possibly because the amount of time between sections - // is too small for the profiler to measure - IProfiler profiler = mc.getProfiler(); - profiler.pop(); // get out of "terrain" - profiler.push("LOD"); - - renderer.drawLODs(lodDim, partialTicks, mc.getProfiler()); - - profiler.pop(); // end LOD - profiler.push("terrain"); // restart "terrain" - - - // these can't be set until after the buffers are built (in renderer.drawLODs) - // otherwise the buffers may be set to the wrong size, or not changed at all - previousChunkRenderDistance = mc.options.renderDistance; - previousLodMultiplierDistance = LodConfig.CLIENT.lodChunkRadiusMultiplier.get(); - } - - - private void applyConfigOverrides() - { - // remind the developer(s). that config override is active - if (!configOverrideReminderPrinted) - { - mc.player.sendMessage(new StringTextComponent("Debug settings enabled!"), mc.player.getUUID()); - configOverrideReminderPrinted = true; - } - -// LodConfig.CLIENT.drawLODs.set(true); + public static final Logger LOGGER = LogManager.getLogger("LOD"); + + private static LodWorld lodWorld = new LodWorld(); + private static LodBuilder lodBuilder = new LodBuilder(); + private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder(lodBuilder); + private static LodRenderer renderer = new LodRenderer(lodBufferBuilder); + + private boolean configOverrideReminderPrinted = false; + + Minecraft mc = Minecraft.getInstance(); + + /** This is used to determine if the LODs should be regenerated */ + public static int previousChunkRenderDistance = 0; + /** This is used to determine if the LODs should be regenerated */ + public static int previousLodMultiplierDistance = 0; + + /** can be set if we want to recalculate variables related + * to the LOD view distance */ + private boolean recalculateWidths = false; + + + public ClientProxy() + { + + } + + + //==============// + // render event // + //==============// + + /** + * Do any setup that is required to draw LODs + * and then tell the LodRenderer to draw. + */ + public void renderLods(float partialTicks) + { + if (mc == null || mc.player == null || !lodWorld.getIsWorldLoaded()) + return; + + + viewDistanceChangedEvent(); + + LodDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType()); + if (lodDim == null) + return; + + playerMoveEvent(lodDim); + + + // comment out when creating a release + applyConfigOverrides(); + + + // Note to self: + // if "unspecified" shows up in the pie chart, it is + // possibly because the amount of time between sections + // is too small for the profiler to measure + IProfiler profiler = mc.getProfiler(); + profiler.pop(); // get out of "terrain" + profiler.push("LOD"); + + renderer.drawLODs(lodDim, partialTicks, mc.getProfiler()); + + profiler.pop(); // end LOD + profiler.push("terrain"); // restart "terrain" + + + // these can't be set until after the buffers are built (in renderer.drawLODs) + // otherwise the buffers may be set to the wrong size, or not changed at all + previousChunkRenderDistance = mc.options.renderDistance; + previousLodMultiplierDistance = LodConfig.CLIENT.lodChunkRadiusMultiplier.get(); + } + + + private void applyConfigOverrides() + { + // remind the developer(s). that config override is active + if (!configOverrideReminderPrinted) + { + mc.player.sendMessage(new StringTextComponent("Debug settings enabled!"), mc.player.getUUID()); + configOverrideReminderPrinted = true; + } + + // LodConfig.CLIENT.drawLODs.set(true); LodConfig.CLIENT.debugMode.set(false); - - LodConfig.CLIENT.maxDrawDetail.set(LodDetail.FULL); - LodConfig.CLIENT.maxGenerationDetail.set(LodDetail.FULL); - - LodConfig.CLIENT.lodChunkRadiusMultiplier.set(16); - LodConfig.CLIENT.fogDistance.set(FogDistance.FAR); - LodConfig.CLIENT.fogDrawOverride.set(FogDrawOverride.ALWAYS_DRAW_FOG_FANCY); - LodConfig.CLIENT.shadingMode.set(ShadingMode.DARKEN_SIDES); -// LodConfig.CLIENT.brightnessMultiplier.set(1.0); -// LodConfig.CLIENT.saturationMultiplier.set(1.0); - - LodConfig.CLIENT.distanceGenerationMode.set(DistanceGenerationMode.FEATURES); - LodConfig.CLIENT.allowUnstableFeatureGeneration.set(false); - LodConfig.CLIENT.lodChunkRenderDistane.set(128); - LodConfig.CLIENT.lodDistanceCalculatorType.set(DistanceCalculatorType.QUADRATIC); - LodConfig.CLIENT.lodQuality.set(1); - LodConfig.CLIENT.allowUnstableFeatureGeneration.set(false); - - // has to be set in the config file -// LodConfig.CLIENT.numberOfWorldGenerationThreads.set(16); - } - - - //==============// - // forge events // - //==============// - - @SubscribeEvent - public void chunkLoadEvent(ChunkEvent.Load event) - { - lodBuilder.generateLodNodeAsync(event.getChunk(), lodWorld, event.getWorld(), DistanceGenerationMode.SERVER); - } - - @SubscribeEvent - public void worldSaveEvent(WorldEvent.Save event) - { - if (lodWorld != null) - lodWorld.saveAllDimensions(); - } - - @SubscribeEvent - public void worldLoadEvent(WorldEvent.Load event) - { - // the player just loaded a new world/dimension - lodWorld.selectWorld(LodUtil.getWorldID(event.getWorld())); - // make sure the correct LODs are being rendered - // (if this isn't done the previous world's LODs may be drawn) - renderer.regenerateLODsNextFrame(); - } - - @SubscribeEvent - public void worldUnloadEvent(WorldEvent.Unload event) - { - // the player just unloaded a world/dimension - - if (mc.getConnection().getLevel() == null) - { - // if this isn't done unfinished tasks may be left in the queue - // preventing new LodChunks form being generated - LodNodeGenWorker.restartExecuterService(); - - lodBufferBuilder.numberOfChunksWaitingToGenerate.set(0); - // the player has disconnected from a server - lodWorld.deselectWorld(); - } - } - - - @SubscribeEvent - public void blockChangeEvent(BlockEvent event) - { - if (event.getClass() == BlockEvent.BreakEvent.class || - event.getClass() == BlockEvent.EntityPlaceEvent.class || - event.getClass() == BlockEvent.EntityMultiPlaceEvent.class || - event.getClass() == BlockEvent.FluidPlaceBlockEvent.class || - event.getClass() == BlockEvent.PortalSpawnEvent.class) - { - // recreate the LOD where the blocks were changed - lodBuilder.generateLodNodeAsync(event.getWorld().getChunk(event.getPos()), lodWorld, event.getWorld()); - } - } - - - //==================// - // frame LOD events // - //==================// - - /** - * Re-centers the given LodDimension if it needs to be. - */ - private void playerMoveEvent(LodDimension lodDim) - { - // make sure the dimension is centered - RegionPos playerRegionPos = new RegionPos(mc.player.blockPosition()); - RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterX(), playerRegionPos.z - lodDim.getCenterZ()); - if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0) - { - lodWorld.saveAllDimensions(); - lodDim.move(worldRegionOffset); - - //LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z + "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ()); - } - } - - - /** - * Re-sizes all LodDimensions if they needs to be. - */ - private void viewDistanceChangedEvent() - { - // calculate how wide the dimension(s) should be in regions - //int chunksWide = (mc.options.renderDistance * 2) * LodConfig.CLIENT.lodChunkRadiusMultiplier.get(); - int chunksWide = 8 * 2 * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() + 1; - int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS); - newWidth = (newWidth % 2 == 0) ? (newWidth += 1) : (newWidth += 2); // make sure we have a odd number of regions - - // do the dimensions need to change in size? - if (lodBuilder.defaultDimensionWidthInRegions != newWidth) - { - // TODO make this async - - // update the dimensions to fit the new width - lodWorld.resizeDimensionRegionWidth(newWidth); - lodBuilder.defaultDimensionWidthInRegions = newWidth; - renderer.setupBuffers(newWidth); - - //LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth ); - } - } - - - //================// - // public getters // - //================// - - public static LodWorld getLodWorld() - { - return lodWorld; - } - - public static LodBuilder getLodBuilder() - { - return lodBuilder; - } - - public static LodRenderer getRenderer() - { - return renderer; - } + + LodConfig.CLIENT.maxDrawDetail.set(LodDetail.FULL); + LodConfig.CLIENT.maxGenerationDetail.set(LodDetail.FULL); + + LodConfig.CLIENT.lodChunkRadiusMultiplier.set(16); + LodConfig.CLIENT.fogDistance.set(FogDistance.FAR); + LodConfig.CLIENT.fogDrawOverride.set(FogDrawOverride.ALWAYS_DRAW_FOG_FANCY); + LodConfig.CLIENT.shadingMode.set(ShadingMode.DARKEN_SIDES); + // LodConfig.CLIENT.brightnessMultiplier.set(1.0); + // LodConfig.CLIENT.saturationMultiplier.set(1.0); + + LodConfig.CLIENT.distanceGenerationMode.set(DistanceGenerationMode.FEATURES); + LodConfig.CLIENT.allowUnstableFeatureGeneration.set(false); + LodConfig.CLIENT.lodChunkRenderDistane.set(96); + LodConfig.CLIENT.lodDistanceCalculatorType.set(DistanceCalculatorType.QUADRATIC); + LodConfig.CLIENT.lodQuality.set(1); + LodConfig.CLIENT.allowUnstableFeatureGeneration.set(false); + + // has to be set in the config file + // LodConfig.CLIENT.numberOfWorldGenerationThreads.set(16); + } + + + //==============// + // forge events // + //==============// + + @SubscribeEvent + public void chunkLoadEvent(ChunkEvent.Load event) + { + lodBuilder.generateLodNodeAsync(event.getChunk(), lodWorld, event.getWorld(), DistanceGenerationMode.SERVER); + } + + @SubscribeEvent + public void worldSaveEvent(WorldEvent.Save event) + { + if (lodWorld != null) + lodWorld.saveAllDimensions(); + } + + @SubscribeEvent + public void worldLoadEvent(WorldEvent.Load event) + { + // the player just loaded a new world/dimension + lodWorld.selectWorld(LodUtil.getWorldID(event.getWorld())); + // make sure the correct LODs are being rendered + // (if this isn't done the previous world's LODs may be drawn) + renderer.regenerateLODsNextFrame(); + } + + @SubscribeEvent + public void worldUnloadEvent(WorldEvent.Unload event) + { + // the player just unloaded a world/dimension + + if (mc.getConnection().getLevel() == null) + { + // if this isn't done unfinished tasks may be left in the queue + // preventing new LodChunks form being generated + LodNodeGenWorker.restartExecuterService(); + + lodBufferBuilder.numberOfChunksWaitingToGenerate.set(0); + // the player has disconnected from a server + lodWorld.deselectWorld(); + + + // hopefully this should reduce issues related to the buffer builder + // breaking when changing worlds. + renderer.destroyBuffers(); + recalculateWidths = true; + } + } + + + @SubscribeEvent + public void blockChangeEvent(BlockEvent event) + { + if (event.getClass() == BlockEvent.BreakEvent.class || + event.getClass() == BlockEvent.EntityPlaceEvent.class || + event.getClass() == BlockEvent.EntityMultiPlaceEvent.class || + event.getClass() == BlockEvent.FluidPlaceBlockEvent.class || + event.getClass() == BlockEvent.PortalSpawnEvent.class) + { + // recreate the LOD where the blocks were changed + lodBuilder.generateLodNodeAsync(event.getWorld().getChunk(event.getPos()), lodWorld, event.getWorld()); + } + } + + + //==================// + // frame LOD events // + //==================// + + /** + * Re-centers the given LodDimension if it needs to be. + */ + private void playerMoveEvent(LodDimension lodDim) + { + // make sure the dimension is centered + RegionPos playerRegionPos = new RegionPos(mc.player.blockPosition()); + RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterX(), playerRegionPos.z - lodDim.getCenterZ()); + if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0) + { + lodWorld.saveAllDimensions(); + lodDim.move(worldRegionOffset); + + //LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z + "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ()); + } + } + + + /** + * Re-sizes all LodDimensions if they needs to be. + */ + private void viewDistanceChangedEvent() + { + // calculate how wide the dimension(s) should be in regions + //int chunksWide = (mc.options.renderDistance * 2) * LodConfig.CLIENT.lodChunkRadiusMultiplier.get(); + int chunksWide = 8 * 2 * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() + 1; + int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS); + newWidth = (newWidth % 2 == 0) ? (newWidth += 1) : (newWidth += 2); // make sure we have a odd number of regions + + // do the dimensions need to change in size? + if (lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths) + { + // TODO make this async + + // update the dimensions to fit the new width + lodWorld.resizeDimensionRegionWidth(newWidth); + lodBuilder.defaultDimensionWidthInRegions = newWidth; + renderer.setupBuffers(newWidth); + + recalculateWidths = false; + //LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth ); + } + } + + + //================// + // public getters // + //================// + + public static LodWorld getLodWorld() + { + return lodWorld; + } + + public static LodBuilder getLodBuilder() + { + return lodBuilder; + } + + public static LodRenderer getRenderer() + { + return renderer; + } } diff --git a/src/main/java/com/seibel/lod/render/LodRenderer.java b/src/main/java/com/seibel/lod/render/LodRenderer.java index 591c19b1c..ab6434e0b 100644 --- a/src/main/java/com/seibel/lod/render/LodRenderer.java +++ b/src/main/java/com/seibel/lod/render/LodRenderer.java @@ -672,6 +672,14 @@ public class LodRenderer // the newly created buffers from the lodBufferBuilder vbos = lodBufferBuilder.getVertexBuffers(); } + + /** + * Calls the BufferBuilder's destroyBuffers method. + */ + public void destroyBuffers() + { + lodBufferBuilder.destroyBuffers(); + } private double getFov(float partialTicks, boolean useFovSetting)