/* * This file is part of the LOD Mod, licensed under the GNU GPL v3 License. * * Copyright (C) 2020 James Seibel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.seibel.lod.builders; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.locks.ReentrantLock; import org.lwjgl.opengl.GL11; import com.seibel.lod.handlers.LodConfig; import com.seibel.lod.objects.LevelPos; import com.seibel.lod.objects.LodDataPoint; 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.DetailDistanceUtil; import com.seibel.lod.util.LodThreadFactory; import com.seibel.lod.util.LodUtil; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.vertex.VertexBuffer; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; /** * This object is used to create NearFarBuffer objects. * * @author James Seibel * @version 8-24-2021 */ public class LodBufferBuilder { /** This holds the thread used to generate new LODs off the main thread. */ private ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " - main")); /** This holds the threads used to generate buffers. */ private ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new LodThreadFactory(this.getClass().getSimpleName() + " - builder")); /** 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 new LOD buffers have been generated * and are waiting to be swapped with the drawable buffers */ private boolean switchVbos = false; /** 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; /** this is used to prevent multiple threads creating, destroying, or using the buffers at the same time */ private ReentrantLock bufferLock = new ReentrantLock(); public LodBufferBuilder() { } /** * 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."); 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(() -> { bufferLock.lock(); try { long treeStart = System.currentTimeMillis(); long treeEnd = System.currentTimeMillis(); long startTime = System.currentTimeMillis(); ArrayList> builderThreads = new ArrayList<>(lodDim.regions.length * lodDim.regions.length); startBuffers(); // =====================// // RENDERING PART // // =====================// 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; byte detailLevel = LodConfig.CLIENT.maxGenerationDetail.get().detailLevel; Callable bufferBuildingThread = () -> { byte detailToRender; boolean zFix; Set setOfPosToRender = new HashSet<>(); for (byte detail = detailLevel; detail <= LodUtil.REGION_DETAIL_LEVEL; detail++) { detailToRender = detail; if(detail > detailToRender){ zFix = false; }else{ detailToRender = detail; zFix = true; } setOfPosToRender.addAll(lodDim.getDataToRender( regionPos, playerBlockPosRounded.getX(), playerBlockPosRounded.getZ(), DetailDistanceUtil.getDistanceRendering(detail), DetailDistanceUtil.getDistanceRendering(detail + 1), detailToRender, zFix)); } for (LevelPos posToRender : setOfPosToRender) { LevelPos chunkPos = posToRender.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(posToRender)) { try { LodDataPoint lodData = lodDim.getData(posToRender); if (lodData != null) { /*We check for adjacent data*/ LodDataPoint[][] adjData = new LodDataPoint[2][2]; LevelPos adjPos; for(int x : new int[]{0,1}){ adjPos = new LevelPos(posToRender.detailLevel, posToRender.posX + x*2-1, posToRender.posZ); adjData[0][x] = lodDim.getData(adjPos);/* if(setOfPosToRender.contains(adjPos)){ System.out.println("yup"); adjData[0][x] = lodDim.getData(adjPos); }*/ } for(int z : new int[]{0,1}){ adjPos = new LevelPos(posToRender.detailLevel, posToRender.posX, posToRender.posZ + z*2-1); adjData[1][z] = lodDim.getData(adjPos);/* if(setOfPosToRender.contains(adjPos)){ System.out.println("yup2"); adjData[1][z] = lodDim.getData(adjPos); }*/ } LodConfig.CLIENT.lodTemplate.get().template.addLodToBuffer(currentBuffer, playerBlockPos, lodData, adjData, posToRender, renderer.debugging); } } catch (ArrayIndexOutOfBoundsException e) { return false; } } }// for pos to in list to render // the thread executed successfully return true; };// buffer builder worker thread builderThreads.add(bufferBuildingThread); }// region z }// region z long renderStart = System.currentTimeMillis(); // wait for all threads to finish List> futures = bufferBuilderThreads.invokeAll(builderThreads); for(Future future : futures) { // the future will be false if its thread failed if (!future.get()) { ClientProxy.LOGGER.warn("LodBufferBuilder ran into trouble and had to start over."); closeBuffers(); return; } } long renderEnd = System.currentTimeMillis(); // finish the buffer building closeBuffers(); // upload the new buffers uploadBuffers(); long endTime = System.currentTimeMillis(); long buildTime = endTime - startTime; long treeTime = treeEnd - treeStart; long renderingTime = renderEnd - renderStart; ClientProxy.LOGGER.info("Buffer Build time: " + buildTime + " ms" + '\n' + "Tree cutting time: " + treeTime + " ms" + '\n' + "Rendering time: " + renderingTime + " 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(); bufferLock.unlock(); } }); mainGenThread.execute(thread); return; } //===============================// // BufferBuilder related methods // //===============================// /** * Called from the LodRenderer to create the * BufferBuilders.

* * May have to wait for the bufferLock to open. */ public void setupBuffers(int numbRegionsWide, int bufferMaxCapacity) { bufferLock.lock(); 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); } } bufferLock.unlock(); } /** * sets the buffers and Vbos to null, forcing them to be recreated.

* * May have to wait for the bufferLock to open. */ public void destroyBuffers() { bufferLock.lock(); buildableBuffers = null; buildableVbos = null; drawableVbos = null; bufferLock.unlock(); } /** * Calls begin on each of the buildable BufferBuilders. */ private 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. */ private 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. * */ private 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() { // don't wait for the lock to open // since this is called on the main render thread if (bufferLock.tryLock()) { VertexBuffer[][] tmp = drawableVbos; drawableVbos = buildableVbos; buildableVbos = tmp; // the vbos have been swapped switchVbos = false; bufferLock.unlock(); } 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; } }