Buffers: Basically redid the buffer management

Note: Backface Culling currently disabled
This commit is contained in:
tom lee
2022-01-03 00:01:37 +08:00
parent f80c43385a
commit d72805d1fe
9 changed files with 1042 additions and 931 deletions
@@ -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))
File diff suppressed because it is too large Load Diff
@@ -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. <br><br>
@@ -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++)
@@ -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;
}
@@ -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<Boolean> {
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<gridSize*gridSize; i++) {
add(i, false);
}
}
}
public static class LagSpikeCatcher {
long timer = System.nanoTime();
public LagSpikeCatcher() {}
public void end(String source) {
timer = System.nanoTime() - timer;
if (timer> 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<LodVertexBuffer[]> 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<vbos.gridSize; regionX++) {
for (int regionZ=lowRegionZ; regionZ<vbos.gridSize; regionZ++) {
if (vbos.get(regionX, regionZ) == null) continue;
if (cullingDisabled || RenderUtil.isRegionInViewFrustum(MC_RENDER.getCameraBlockPosition(),
MC_RENDER.getLookAtVector(),
vbosCenterX + LodUtil.convertLevelPos(x - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL , LodUtil.BLOCK_DETAIL_LEVEL),
vbosCenterZ + LodUtil.convertLevelPos(z - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL , LodUtil.BLOCK_DETAIL_LEVEL)))
{
// actual rendering
int bufferId = 0;
for (int i = 0; i < vbos[x][z].length; i++)
{
bufferId = (storageBufferIds != null && usingBufferStorage) ? storageBufferIds[x][z][i] : vbos[x][z][i].id;
if (bufferId==0) continue;
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, bufferId);
shaderProgram.bindVertexBuffer(bufferId);
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbos[x][z][i].vertexCount);
MC_RENDER.getLookAtVector(), regionX, regionZ)) {
for (LodVertexBuffer vbo : vbos.get(regionX, regionZ)) {
if (vbo == null) continue;
if (vbo.vertexCount == 0) continue;
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
shaderProgram.bindVertexBuffer(vbo.id);
drawCall++;
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbo.vertexCount);
}
}
}
}
//if (drawCall!=0)
// ClientApi.LOGGER.info("DrawCall Count: "+drawCall);
//================//
// render cleanup //
//================//
draw.end("LodDraw");
profiler.popPush("LOD cleanup");
LagSpikeCatcher drawCleanup = new LagSpikeCatcher();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
@@ -340,7 +353,7 @@ public class LodRenderer
// clear the depth buffer so everything is drawn over the LODs
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
drawCleanup.end("LodDrawCleanup");
// end of internal LOD profiling
profiler.pop();
}
@@ -367,7 +380,7 @@ public class LodRenderer
/** Create all buffers that will be used. */
public void setupBuffers(LodDimension lodDim)
{
lodBufferBuilderFactory.setupBuffers(lodDim);
lodBufferBuilderFactory.allBuffersRequireReset = true;
}
private Color getFogColor(float partialTicks)
@@ -393,7 +406,7 @@ public class LodRenderer
* (since AxisAlignedBoundingBoxes (LODs) use doubles and thus have a higher
* accuracy vs the model view matrix, which only uses floats)
*/
private Mat4f translateModelViewMatrix(Mat4f mcModelViewMatrix, float partialTicks)
private Mat4f translateModelViewMatrix(Mat4f mcModelViewMatrix, float partialTicks, int vbosCenterX, int vbosCenterZ)
{
// get all relevant camera info
Vec3d projectedView = MC_RENDER.getCameraExactPosition();
@@ -416,7 +429,7 @@ public class LodRenderer
* Similar to translateModelViewMatrix (above),
* but for the camera position
*/
private Vec3f getTranslatedCameraPos()
private Vec3f getTranslatedCameraPos(int vbosCenterX, int vbosCenterZ)
{
//int worldCenterX = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterX, LodUtil.BLOCK_DETAIL_LEVEL);
//int worldCenterZ = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterZ, LodUtil.BLOCK_DETAIL_LEVEL);
@@ -484,138 +497,127 @@ public class LodRenderer
fullRegen = true;
}
/**
* Replace the current Vertex Buffers with the newly
* created buffers from the lodBufferBuilder. <br><br>
* <p>
* 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<AbstractChunkPosWrapper> 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<AbstractChunkPosWrapper> 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];
}
}
@@ -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) ||
@@ -0,0 +1,76 @@
package com.seibel.lod.core.util;
import java.util.ArrayList;
import java.util.List;
public class GridList<T> extends ArrayList<T> implements List<T> {
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<T> subGrid(int gridCentreToEdge) {
int centreIndex = size() / 2;
GridList<T> subGrid = new GridList<T>(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();
}
}
@@ -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
@@ -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<T> extends ArrayList<T> implements List<T> {
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*gridSize; i++) {
super.add(null);
}
}
public int getCenterX() {return centerX;}
public int getCenterY() {return centerY;}
// return null if x,y is outside of the grid
public T get(int x, int y) {
x = x-centerX+gridCentreToEdge;
y = y-centerY+gridCentreToEdge;
return _getDirect(x,y);
}
// return null if x,y is outside of the grid
public T setAndGet(int x, int y, T t) {
x = x-centerX+gridCentreToEdge;
y = y-centerY+gridCentreToEdge;
return _setDirect(x,y, t) ? t : null;
}
private final T _getDirect(int x, int y) {
if (x<0 || x>=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<T> 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();
}
}