Files
distant-horizons-sharded/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java
T
2021-08-27 12:02:02 +02:00

484 lines
18 KiB
Java

/*
* 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 <https://www.gnu.org/licenses/>.
*/
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.*;
import java.util.concurrent.locks.ReentrantLock;
import com.seibel.lod.objects.DataPoint;
import net.minecraft.world.chunk.Chunk;
import org.lwjgl.opengl.GL11;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LevelPos.LevelPos;
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;
import javax.xml.crypto.Data;
/**
* 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.
* <br>
* This method will write to the drawable near and far buffers.
* <br>
* 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<Callable<Boolean>> nodeToRenderThreads = new ArrayList<>(lodDim.regions.length * lodDim.regions.length);
ArrayList<Callable<Boolean>> builderThreads = new ArrayList<>(lodDim.regions.length * lodDim.regions.length);
startBuffers();
// =====================//
// RENDERING PART //
// =====================//
Object[][] nodeToRenderMatrix = new Object[lodDim.regions.length][lodDim.regions.length];
for (int xRegion = 0; xRegion < lodDim.regions.length; xRegion++)
{
for (int zRegion = 0; zRegion < lodDim.regions.length; zRegion++)
{
nodeToRenderMatrix[xRegion][zRegion] = new ConcurrentSkipListMap<>(LevelPos.getComparator());
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;
final int xR = xRegion;
final int zR = zRegion;
Callable<Boolean> dataToRenderThread = () ->
{
byte detailToRender;
boolean zFix;
for (byte detail = detailLevel; detail <= LodUtil.REGION_DETAIL_LEVEL; detail++)
{
detailToRender = detail;
lodDim.getDataToRender(
(ConcurrentSkipListMap<LevelPos, List<Integer>>) nodeToRenderMatrix[xR][zR],
regionPos,
playerBlockPosRounded.getX(),
playerBlockPosRounded.getZ(),
DetailDistanceUtil.getDistanceRendering(detail),
DetailDistanceUtil.getDistanceRendering(detail + 1),
detailToRender,
true);
}
// the thread executed successfully
return true;
};// buffer builder worker thread
nodeToRenderThreads.add(dataToRenderThread);
}// region z
}// region z
long renderStart = System.currentTimeMillis();
// wait for all threads to finish
List<Future<Boolean>> futures = bufferBuilderThreads.invokeAll(nodeToRenderThreads);
for (Future<Boolean> 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();
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;
final int xR = xRegion;
final int zR = zRegion;
Callable<Boolean> bufferBuildingThread = () ->
{
LevelPos adjPos = new LevelPos();
for (LevelPos posToRender : ((ConcurrentSkipListMap<LevelPos, List<Integer>>) nodeToRenderMatrix[xR][zR]).keySet())
{
LevelPos chunkPos = posToRender.getConvertedLevelPos(LodUtil.CHUNK_DETAIL_LEVEL);
// skip any chunks that Minecraft is going to render
if (renderer.vanillaRenderedChunks.contains(new ChunkPos(chunkPos.posX, chunkPos.posZ)))
{
continue;
}
try
{
if (lodDim.doesDataExist(posToRender.clone()))
{
short[] lodData = lodDim.getData(posToRender);
short[][][] adjData = new short[2][2][];
for (int x : new int[]{0, 1})
{
adjPos.changeParameters(posToRender.detailLevel, posToRender.posX + x * 2 - 1, posToRender.posZ);
if (!renderer.vanillaRenderedChunks.contains(adjPos.getChunkPos()))
adjData[0][x] = lodDim.getData(adjPos);
}
for (int z : new int[]{0, 1})
{
adjPos.changeParameters(posToRender.detailLevel, posToRender.posX, posToRender.posZ + z * 2 - 1);
if (!renderer.vanillaRenderedChunks.contains(adjPos.getChunkPos()))
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 renderBufferStart = System.currentTimeMillis();
// wait for all threads to finish
List<Future<Boolean>> futuresBuffer = bufferBuilderThreads.invokeAll(builderThreads);
for (Future<Boolean> future : futuresBuffer)
{
// 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 renderBufferEnd = 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. <br><br>
* <p>
* 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. <br><br>
* <p>
* 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;
}
}