adjData = ThreadMapUtil.getAdjDataArray(maxVerticalData);
+
+ //We ask the lod dimension which block we have to render given the player position
+ PosToRenderContainer posToRender = setsToRender.get(regPos.x, regPos.z);
+ //previous setToRender cache
+ if (posToRender == null) {
+ posToRender = setsToRender.setAndGet(regPos.x, regPos.z, new PosToRenderContainer(minDetail, regPos.x, regPos.z));
+ }
+ posToRender.clear(minDetail, regPos.x, regPos.z);
+ lodDim.getPosToRender(posToRender, regPos, playerX, playerZ);
+
+ for (int index = 0; index < posToRender.getNumberOfPos(); index++)
+ {
+ int bufferIndex = index % currentBuffers.length;
+ byte detailLevel = posToRender.getNthDetailLevel(index);
+ int posX = posToRender.getNthPosX(index);
+ int posZ = posToRender.getNthPosZ(index);
+
+ int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkX;
+ int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkZ;
+
+ // Currently fixing below
+ // FIXME: We don't need to ignore rendered chunks! Just build it and leave it for the renderer to decide!
+ //We don't want to render this fake block if
+ //The block is inside the render distance with, is not bigger than a chunk and is positioned in a chunk set as vanilla rendered
+ //
+ //The block is in the player chunk or in a chunk adjacent to the player
+ //if(isThisPositionGoingToBeRendered(detailLevel, posX, posZ, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance))
+ //{
+ // continue;
+ //}
+
+ //we check if the block to render is not in player chunk
+ boolean posNotInPlayerChunk = !(chunkXdist == 0 && chunkZdist == 0);
+
+ // We extract the adj data in the four cardinal direction
+
+ // we first reset the adjShadeDisabled. This is used to disable the shade on the border when we have transparent block like water or glass
+ // to avoid having a "darker border" underground
+ Arrays.fill(adjShadeDisabled, false);
+
+ //We check every adj block in each direction
+ for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS)
+ {
+ int xAdj = posX + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).x;
+ int zAdj = posZ + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).z;
+ chunkXdist = LevelPosUtil.getChunkPos(detailLevel, xAdj) - playerChunkX;
+ chunkZdist = LevelPosUtil.getChunkPos(detailLevel, zAdj) - playerChunkZ;
+ boolean adjPosInPlayerChunk = (chunkXdist == 0 && chunkZdist == 0);
+
+ //If the adj block is rendered in the same region and with same detail
+ // and is positioned in a place that is not going to be rendered by vanilla game
+ // then we can set this position as adj
+ // We avoid cases where the adjPosition is in player chunk while the position is not
+ // to always have a wall underwater
+ if(posToRender.contains(detailLevel, xAdj, zAdj)
+ //&& !isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance)
+ && !(posNotInPlayerChunk && adjPosInPlayerChunk))
+ {
+ for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, xAdj, zAdj); verticalIndex++)
+ {
+ long data = lodDim.getData(detailLevel, xAdj, zAdj, verticalIndex);
+ adjShadeDisabled[VertexOptimizer.DIRECTION_INDEX.get(lodDirection)] = false;
+ adjData.get(lodDirection)[verticalIndex] = data;
+ }
+ }
+ else
+ {
+ //Otherwise, we check if this position is
+ long data = lodDim.getSingleData(detailLevel, xAdj, zAdj);
+
+ adjData.get(lodDirection)[0] = DataPointUtil.EMPTY_DATA;
+
+ if ((//isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance) ||
+ (posNotInPlayerChunk && adjPosInPlayerChunk))
+ && !DataPointUtil.isVoid(data))
+ {
+ adjShadeDisabled[VertexOptimizer.DIRECTION_INDEX.get(lodDirection)] = DataPointUtil.getAlpha(data) < 255;
+ }
+ }
+ }
+
+ // We render every vertical lod present in this position
+ // We only stop when we find a block that is void or non-existing block
+ long data;
+ for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ); verticalIndex++)
+ {
+
+ //we get the above block as adj UP
+ if (verticalIndex > 0)
+ adjData.get(LodDirection.UP)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex - 1);
+ else
+ adjData.get(LodDirection.UP)[0] = DataPointUtil.EMPTY_DATA;
+
+
+ //we get the below block as adj DOWN
+ if (verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ) - 1)
+ adjData.get(LodDirection.DOWN)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex + 1);
+ else
+ adjData.get(LodDirection.DOWN)[0] = DataPointUtil.EMPTY_DATA;
+
+ //We extract the data to render
+ data = lodDim.getData(detailLevel, posX, posZ, verticalIndex);
+
+ //If the data is not renderable (Void or non-existing) we stop since there is no data left in this position
+ if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data))
+ break;
+
+ //We send the call to create the vertices
+ CubicLodTemplate.addLodToBuffer(currentBuffers[bufferIndex], vboX, vboZ, data, adjData,
+ detailLevel, posX, posZ, vertexOptimizer, debugMode, adjShadeDisabled, cullingRangeX, cullingRangeZ);
+ }
+
+ } // for pos to in list to render
+ // the thread executed successfully
+ return true;
+ }
+
+
+
+ @Deprecated
private boolean isThisPositionGoingToBeRendered(byte detailLevel, int posX, int posZ, int chunkPosX, int chunkPosZ, boolean[][] vanillaRenderedChunks, int gameChunkRenderDistance){
-
-
// skip any chunks that Minecraft is going to render
int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - chunkPosX;
int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - chunkPosZ;
@@ -557,125 +598,27 @@ public class LodBufferBuilderFactory
*
* May have to wait for the bufferLock to open.
*/
- public void setupBuffers(LodDimension lodDimension)
- {
- try
- {
- bufferLock.lock();
-
- int numbRegionsWide = lodDimension.getWidth();
- long regionMemoryRequired;
- int numberOfBuffers;
-
- GLProxy glProxy = GLProxy.getInstance();
- GLProxyContext oldContext = glProxy.getGlContext();
- glProxy.setGlContext(GLProxyContext.LOD_BUILDER);
-
-
- previousRegionWidth = numbRegionsWide;
- numberOfBuffersPerRegion = new int[numbRegionsWide][numbRegionsWide];
- buildableBuffers = new LodBufferBuilder[numbRegionsWide][numbRegionsWide][];
-
- buildableVbos = new LodVertexBuffer[numbRegionsWide][numbRegionsWide][];
- drawableVbos = new LodVertexBuffer[numbRegionsWide][numbRegionsWide][];
-
- if (glProxy.bufferStorageSupported)
- {
- buildableStorageBufferIds = new int[numbRegionsWide][numbRegionsWide][];
- drawableStorageBufferIds = new int[numbRegionsWide][numbRegionsWide][];
+ private void setupBuffers(int regionX, int regionZ) {
+ //TODO: Impl actual multibuffers
+ //int regionMemoryRequired = DEFAULT_MEMORY_ALLOCATION;
+ //int numberOfBuffers;
+ LodVertexBuffer[] vbos = buildableVbos.get(regionX, regionZ);
+ if (vbos != null)
+ for (LodVertexBuffer vbo : vbos) {
+ if (vbo!=null) vbo.close();
}
-
- for (int x = 0; x < numbRegionsWide; x++)
- {
- for (int z = 0; z < numbRegionsWide; z++)
- {
- regionMemoryRequired = DEFAULT_MEMORY_ALLOCATION;
-
- // if the memory required is greater than the max buffer
- // capacity, divide the memory across multiple buffers
- if (regionMemoryRequired > LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY)
- {
- numberOfBuffers = (int) regionMemoryRequired / LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY + 1;
-
- // TODO shouldn't this be determined with regionMemoryRequired?
- // always allocating the max memory is a bit expensive isn't it?
- regionMemoryRequired = LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY;
- numberOfBuffersPerRegion[x][z] = numberOfBuffers;
- buildableBuffers[x][z] = new LodBufferBuilder[numberOfBuffers];
- buildableVbos[x][z] = new LodVertexBuffer[numberOfBuffers];
- drawableVbos[x][z] = new LodVertexBuffer[numberOfBuffers];
-
- if (glProxy.bufferStorageSupported)
- {
- buildableStorageBufferIds[x][z] = new int[numberOfBuffers];
- drawableStorageBufferIds[x][z] = new int[numberOfBuffers];
- }
- }
- else
- {
- // we only need one buffer for this region
- numberOfBuffersPerRegion[x][z] = 1;
- buildableBuffers[x][z] = new LodBufferBuilder[1];
- buildableVbos[x][z] = new LodVertexBuffer[1];
- drawableVbos[x][z] = new LodVertexBuffer[1];
-
- if (glProxy.bufferStorageSupported)
- {
- buildableStorageBufferIds[x][z] = new int[1];
- drawableStorageBufferIds[x][z] = new int[1];
- }
- }
-
-
- for (int i = 0; i < numberOfBuffersPerRegion[x][z]; i++)
- {
- buildableBuffers[x][z][i] = new LodBufferBuilder((int) regionMemoryRequired);
-
- buildableVbos[x][z][i] = new LodVertexBuffer();
- drawableVbos[x][z][i] = new LodVertexBuffer();
-
-
- // create the initial mapped buffers (system memory)
- GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, buildableVbos[x][z][i].id);
- GL32.glBufferData(GL32.GL_ARRAY_BUFFER, regionMemoryRequired, GL32.GL_STATIC_DRAW);
- GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
-
- GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, drawableVbos[x][z][i].id);
- GL32.glBufferData(GL32.GL_ARRAY_BUFFER, regionMemoryRequired, GL32.GL_STATIC_DRAW);
- GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
-
-
- if (glProxy.bufferStorageSupported)
- {
- // create the buffer storage (GPU memory)
- buildableStorageBufferIds[x][z][i] = GL44.glGenBuffers();
- GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, buildableStorageBufferIds[x][z][i]);
- GL44.glBufferStorage(GL44.GL_ARRAY_BUFFER, regionMemoryRequired, GL44.GL_DYNAMIC_STORAGE_BIT);
- GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, 0);
-
- drawableStorageBufferIds[x][z][i] = GL44.glGenBuffers();
- GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, drawableStorageBufferIds[x][z][i]);
- GL44.glBufferStorage(GL44.GL_ARRAY_BUFFER, regionMemoryRequired, GL44.GL_DYNAMIC_STORAGE_BIT);
- GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, 0);
- }
- }
- }
- }
-
- glProxy.setGlContext(oldContext);
- }
- catch (Exception e)
+ /*
+ if (regionMemoryRequired > LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY)
{
- ClientApi.LOGGER.info("setupBuffers ran into trouble: " + e.getMessage(), e);
- }
- finally
- {
- // this shouldn't normally happen, but just in case it sill prevent deadlock
- bufferLock.unlock();
- }
+ // TODO shouldn't this be determined with regionMemoryRequired?
+ // always allocating the max memory is a bit expensive isn't it?
+ numberOfBuffers = (int) regionMemoryRequired / LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY + 1;
+ } else {
+ numberOfBuffers = 1;
+ }*/
+ vbos = buildableVbos.setAndGet(regionX, regionZ, new LodVertexBuffer[1]);
}
-
/**
* Sets the buffers and Vbos to null, forcing them to be recreated
* and destroys any bound OpenGL objects.
@@ -684,18 +627,12 @@ public class LodBufferBuilderFactory
* May have to wait for the bufferLock to open.
*/
public void destroyBuffers() {
- int[][][] toBeDeletedBuildableStorageBufferIds;
- int[][][] toBeDeletedDrawableStorageBufferIds;
- LodVertexBuffer[][][] toBeDeletedBuildableVbos;
- LodVertexBuffer[][][] toBeDeletedDrawableVbos;
+ MovableGridList toBeDeletedBuildableVbos;
+ MovableGridList toBeDeletedDrawableVbos;
bufferLock.lock();
try {
- toBeDeletedBuildableStorageBufferIds = buildableStorageBufferIds;
- toBeDeletedDrawableStorageBufferIds = drawableStorageBufferIds;
toBeDeletedBuildableVbos = buildableVbos;
toBeDeletedDrawableVbos = drawableVbos;
- buildableStorageBufferIds = null;
- drawableStorageBufferIds = null;
buildableVbos = null;
drawableVbos = null;
// these don't contain any OpenGL objects, so
@@ -704,346 +641,255 @@ public class LodBufferBuilderFactory
} finally {
bufferLock.unlock();
}
-
// make sure the buffers are deleted in a openGL context
GLProxy.getInstance().recordOpenGlCall(() -> {
-
- // destroy the buffer storages if they aren't already
- if (toBeDeletedBuildableStorageBufferIds != null) {
- for (int x = 0; x < toBeDeletedBuildableStorageBufferIds.length; x++) {
- for (int z = 0; z < toBeDeletedBuildableStorageBufferIds.length; z++) {
- for (int i = 0; i < toBeDeletedBuildableStorageBufferIds[x][z].length; i++) {
- int buildableId = toBeDeletedBuildableStorageBufferIds[x][z][i];
- int drawableId = toBeDeletedDrawableStorageBufferIds[x][z][i];
-
- GL32.glDeleteBuffers(buildableId);
- GL32.glDeleteBuffers(drawableId);
-
- }
+ // destroy the VBOs if they aren't already
+ if (toBeDeletedBuildableVbos != null) {
+ for (LodVertexBuffer[] vbos : toBeDeletedBuildableVbos) {
+ if (vbos == null) continue;
+ for (LodVertexBuffer vbo : vbos) {
+ if (vbo == null) continue;
+ vbo.close();
}
}
}
- // destroy the VBOs if they aren't already
- if (toBeDeletedBuildableVbos != null) {
- for (int i = 0; i < toBeDeletedBuildableVbos.length; i++) {
- for (int j = 0; j < toBeDeletedBuildableVbos.length; j++) {
- for (int k = 0; k < toBeDeletedBuildableVbos[i][j].length; k++) {
- if (toBeDeletedBuildableVbos[i][j][k] != null) {
- int buildableId = toBeDeletedBuildableVbos[i][j][k].id;
- GL32.glDeleteBuffers(buildableId);
- }
- if (toBeDeletedDrawableVbos[i][j][k] != null) {
- int drawableId = toBeDeletedDrawableVbos[i][j][k].id;
- GL32.glDeleteBuffers(drawableId);
- }
- }
+ if (toBeDeletedDrawableVbos != null) {
+ for (LodVertexBuffer[] vbos : toBeDeletedDrawableVbos) {
+ if (vbos == null) continue;
+ for (LodVertexBuffer vbo : vbos) {
+ if (vbo == null) continue;
+ vbo.close();
}
}
}
});
}
-
- /** Calls begin on each of the buildable BufferBuilders. */
- private void startBuffers(boolean fullRegen, LodDimension lodDim)
+
+ /** Upload all buildableBuffers to the GPU. We should already be in the builder context */
+ private void uploadBuffers(List toBeUploaded)
{
- for (int x = 0; x < buildableBuffers.length; x++)
- {
- for (int z = 0; z < buildableBuffers.length; z++)
+ GLProxy glProxy = GLProxy.getInstance();
+ // determine the upload method
+ GpuUploadMethod uploadMethod = glProxy.getGpuUploadMethod();
+
+ // determine the upload timeout
+ int MBPerMS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds();
+ long BPerNS = MBPerMS; // MB -> B = 1/1,000,000. MS -> NS = 1,000,000. So, MBPerMS = BPerNS.
+ long remainingNS = 0; // We don't want to pause for like 0.1 ms... so we store those tiny MS.
+ long bytesUploaded = 0;
+
+ // actually upload the buffers
+ for (RegionPos p : toBeUploaded) {
+ LodBufferBuilder[] buffers = buildableBuffers.get(p.x, p.z);
+ for (int i = 0; i < buffers.length; i++)
{
- if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z))
- {
- for (int i = 0; i < buildableBuffers[x][z].length; i++)
- {
- // FIXME: for some reason BufferBuilder.vertexCounts
- // isn't reset unless this is called, which can cause
- // a false indexOutOfBoundsException
- buildableBuffers[x][z][i].discard();
-
- buildableBuffers[x][z][i].begin(GL32.GL_QUADS, LodUtil.LOD_VERTEX_FORMAT);
- }
+ ByteBuffer uploadBuffer = null;
+ //FIXME: The sonme Buffers aren't closed/end() and causing errors!
+ try {
+ LagSpikeCatcher b = new LagSpikeCatcher();
+ uploadBuffer = buffers[i].getCleanedByteBuffer();
+ b.end("getCleanedByteBuffer");
+ } catch (IndexOutOfBoundsException e) {
+ // NOTE: Temp try/catch for above FIXME.
+ e.printStackTrace();
+ } catch (RuntimeException e) {
+ ClientApi.LOGGER.error(LodBufferBuilderFactory.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage());
+ e.printStackTrace();
+ }
+ if (uploadBuffer == null) continue;
+ LagSpikeCatcher vboU = new LagSpikeCatcher();
+ vboUpload(p, i, uploadBuffer, uploadMethod);
+ vboU.end("vboUpload");
+
+ // upload buffers over an extended period of time
+ // to hopefully prevent stuttering.
+ remainingNS += uploadBuffer.capacity()*BPerNS;
+ bytesUploaded += uploadBuffer.capacity();
+ if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000/60, TimeUnit.MILLISECONDS)) {
+ if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS) remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
+ try {
+ Thread.sleep(remainingNS/1000000, (int) (remainingNS%1000000));
+ } catch (InterruptedException e) {}
+ remainingNS = 0;
}
}
}
+ ClientApi.LOGGER.info("UploadBuffers uploaded "+bytesUploaded+" bytes.");
}
- /** Calls end on each of the buildable BufferBuilders. */
- private void closeBuffers(boolean fullRegen, LodDimension lodDim)
+ /** Uploads the uploadBuffer so the GPU can use it. */
+ private void vboUpload(RegionPos regPos, int iIndex, ByteBuffer uploadBuffer, GpuUploadMethod uploadMethod)
{
- for (int x = 0; x < buildableBuffers.length; x++)
- for (int z = 0; z < buildableBuffers.length; z++)
- for (int i = 0; i < buildableBuffers[x][z].length; i++)
- if (buildableBuffers[x][z][i] != null && buildableBuffers[x][z][i].building() && (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z)))
- buildableBuffers[x][z][i].end();
- }
-
-
- /** Upload all buildableBuffers to the GPU. */
- private void uploadBuffers(boolean fullRegen, LodDimension lodDim)
- {
- GLProxy glProxy = GLProxy.getInstance();
- try
- {
- // make sure we are uploading to the builder context,
- // this helps prevent interference (IE stuttering) with the Minecraft context.
- glProxy.setGlContext(GLProxyContext.LOD_BUILDER);
-
- // determine the upload method
- GpuUploadMethod uploadMethod = glProxy.getGpuUploadMethod();
-
- // determine the upload timeout
- int MBPerMS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds();
- long BPerNS = MBPerMS; // MB -> B = 1/1,000,000. MS -> NS = 1,000,000. So, MBPerMS = BPerNS.
- long remainingNS = 0; // We don't want to pause for like 0.1 ms... so we store those tiny MS.
-
- // actually upload the buffers
- for (int x = 0; x < buildableVbos.length; x++)
- {
- for (int z = 0; z < buildableVbos.length; z++)
- {
- if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z))
- {
- for (int i = 0; i < buildableBuffers[x][z].length; i++)
- {
- ByteBuffer uploadBuffer = null;
- //FIXME: The sonme Buffers aren't closed/end() and causing errors!
- try {
- LagSpikeCatcher b = new LagSpikeCatcher();
- uploadBuffer = buildableBuffers[x][z][i].getCleanedByteBuffer();
- b.end("getCleanedByteBuffer");
- } catch (IndexOutOfBoundsException e) {
- // NOTE: Temp try/catch for above FIXME.
- // e.printStackTrace();
- }
- if (uploadBuffer == null) continue;
- LagSpikeCatcher vboU = new LagSpikeCatcher();
- vboUpload(x,z,i, uploadBuffer, uploadMethod);
- vboU.end("vboUpload");
- LagSpikeCatcher setR = new LagSpikeCatcher();
- lodDim.setRegenRegionBufferByArrayIndex(x, z, false);
- setR.end("setRegenRegionBufferByArrayIndex");
+ boolean useBuffStorage = uploadMethod == GpuUploadMethod.BUFFER_STORAGE;
+ LodVertexBuffer[] vbos = buildableVbos.get(regPos.x, regPos.z);
+
+ if (vbos[iIndex] == null) {
+ vbos[iIndex] = new LodVertexBuffer(useBuffStorage);
+ } else if (vbos[iIndex].isBufferStorage != useBuffStorage) {
+ vbos[iIndex].close();
+ vbos[iIndex] = new LodVertexBuffer(useBuffStorage);
+ }
- // upload buffers over an extended period of time
- // to hopefully prevent stuttering.
- remainingNS += uploadBuffer.capacity()*BPerNS;
- if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000/60, TimeUnit.MILLISECONDS)) {
- if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS) remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
- Thread.sleep(remainingNS/1000000, (int) (remainingNS%1000000));
- remainingNS = 0;
- }
- }
- }
+ LodVertexBuffer vbo = vbos[iIndex];
+ // this is how many points will be rendered
+ vbo.vertexCount = (uploadBuffer.capacity() / LodUtil.LOD_VERTEX_FORMAT.getByteSize());
+
+ // If size is zero, just ignore it.
+ if (uploadBuffer.capacity()==0) return;
+ LagSpikeCatcher bindBuff = new LagSpikeCatcher();
+ bindBuff.end("glBindBuffer vbo.id");
+
+ try {
+ // if possible use the faster buffer storage route
+ if (uploadMethod == GpuUploadMethod.BUFFER_STORAGE)
+ {
+ GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
+ long size = vbo.size;
+ if (size < uploadBuffer.capacity() ||
+ size > uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER*BUFFER_EXPANSION_MULTIPLIER)
+ {
+ int newSize = (int)(uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER);
+ LagSpikeCatcher buffResizeRegen = new LagSpikeCatcher();
+ GL32.glDeleteBuffers(vbo.id);
+ vbo.id = GL32.glGenBuffers();
+ buffResizeRegen.end("glDeleteBuffers BuffStorage resize");
+ LagSpikeCatcher buffResize = new LagSpikeCatcher();
+ GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
+ GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, newSize, GL44.GL_DYNAMIC_STORAGE_BIT);
+ vbo.size = newSize;
+ buffResize.end("glBufferStorage BuffStorage resize");
+ }
+ LagSpikeCatcher buffSubData = new LagSpikeCatcher();
+ GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer);
+ buffSubData.end("glBufferSubData BuffStorage");
+ }
+ else if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING)
+ {
+ GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
+ // no stuttering but high GPU usage
+ // stores everything in system memory instead of GPU memory
+ // making rendering much slower.
+ // Unless the user is running integrated graphics,
+ // in that case this will actually work better than SUB_DATA.
+ long size = vbo.size;
+ if (size < uploadBuffer.capacity() ||
+ size > uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER*BUFFER_EXPANSION_MULTIPLIER)
+ {
+ int newSize = (int) (uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER);
+ LagSpikeCatcher buffResize = new LagSpikeCatcher();
+ GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
+ vbo.size = newSize;
+ buffResize.end("glBufferData BuffMapping resize");
}
+ ByteBuffer vboBuffer;
+ // map buffer range is better since it can be explicitly unsynchronized
+ LagSpikeCatcher buffMap = new LagSpikeCatcher();
+ vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(),
+ GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT);
+ buffMap.end("glMapBufferRange BuffMapping");
+ LagSpikeCatcher buffWrite = new LagSpikeCatcher();
+ vboBuffer.put(uploadBuffer);
+ LagSpikeCatcher buffUnmap = new LagSpikeCatcher();
+ GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER);
+ buffUnmap.end("glUnmapBuffer");
+
+
+ buffWrite.end("WriteData BuffMapping");
+ }
+ else if (uploadMethod == GpuUploadMethod.DATA)
+ {
+ GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
+ // TODO: Check this nonsense comment!
+ // hybrid bufferData //
+ // high stutter, low GPU usage
+ // But simplest/most compatible
+ LagSpikeCatcher buffData = new LagSpikeCatcher();
+ GL32.glBufferData(GL32.GL_ARRAY_BUFFER, uploadBuffer, GL32.GL_STATIC_DRAW);
+ vbo.size = uploadBuffer.capacity();
+ buffData.end("glBufferData Data");
+ }
+ else
+ {
+ GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
+ // TODO: Check this nonsense comment!
+ // hybrid subData/bufferData //
+ // less stutter, low GPU usage
+ long size = vbo.size;
+ if (size < uploadBuffer.capacity() ||
+ size > uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER*BUFFER_EXPANSION_MULTIPLIER)
+ {
+ int newSize = (int)(uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER);
+ LagSpikeCatcher buffResize = new LagSpikeCatcher();
+ GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
+ vbo.size = newSize;
+ buffResize.end("glBufferData SubData resize");
+ }
+ LagSpikeCatcher buffSubData = new LagSpikeCatcher();
+ GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer);
+ buffSubData.end("glBufferSubData SubData");
}
}
catch (Exception e)
{
- // this doesn't appear to be necessary anymore, but just in case.
- ClientApi.LOGGER.error(LodBufferBuilderFactory.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage());
+ ClientApi.LOGGER.error("vboUpload failed: " + e.getClass().getSimpleName());
e.printStackTrace();
- } finally {
- // newSingleThreadExecutor doesn't mean that all jobs will be on a single, same
- // thread. It just means that it can at most use one thread. If there are no
- // jobs for a certain amount of time, or something happened when a job is
- // executing, it could decide to delete the thread, and create a new one for the
- // next job. So we will need to release the gl context.
- LagSpikeCatcher end = new LagSpikeCatcher();
- glProxy.setGlContext(GLProxyContext.NONE);
- end.end("GLSwitchContext");
}
- }
-
- /** Uploads the uploadBuffer so the GPU can use it. */
- private void vboUpload(int xIndex, int zIndex, int iIndex, ByteBuffer uploadBuffer, GpuUploadMethod uploadMethod)
- {
-
-
-
- // get the vbos, buffers, ids, etc.
- int storageBufferId = 0;
- if (buildableStorageBufferIds != null)
- storageBufferId = buildableStorageBufferIds[xIndex][zIndex][iIndex];
-
- LodVertexBuffer vbo = buildableVbos[xIndex][zIndex][iIndex];
-
- // this is how many points will be rendered
- vbo.vertexCount = (uploadBuffer.capacity() / LodUtil.LOD_VERTEX_FORMAT.getByteSize());
- // If size is zero, just ignore it.
- if (uploadBuffer.capacity()==0) return;
-
- // this shouldn't happen, but just to be safe
- if (vbo.id != -1 && GLProxy.getInstance().getGlContext() == GLProxyContext.LOD_BUILDER)
- {
- LagSpikeCatcher bindBuff = new LagSpikeCatcher();
- GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
- bindBuff.end("glBindBuffer vbo.id");
-
- try
- {
- // if possible use the faster buffer storage route
- if (uploadMethod == GpuUploadMethod.BUFFER_STORAGE && storageBufferId != 0)
- {
- GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, storageBufferId);
-
- LagSpikeCatcher getParm = new LagSpikeCatcher();
- long size = GL32.glGetBufferParameteri(GL32.GL_ARRAY_BUFFER, GL32.GL_BUFFER_SIZE);
- getParm.end("glGetBufferParameteri BuffStorage");
- if (size < uploadBuffer.capacity())
- {
- int newSize = (int)(uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER);
- LagSpikeCatcher buffResizeRegen = new LagSpikeCatcher();
- GL32.glDeleteBuffers(storageBufferId);
- buildableStorageBufferIds[xIndex][zIndex][iIndex] = GL32.glGenBuffers();
- buffResizeRegen.end("glDeleteBuffers BuffStorage resize");
- GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, storageBufferId);
- storageBufferId = buildableStorageBufferIds[xIndex][zIndex][iIndex];
- LagSpikeCatcher buffResize = new LagSpikeCatcher();
- GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, newSize, GL44.GL_DYNAMIC_STORAGE_BIT);
- buffResize.end("glBufferStorage BuffStorage resize");
- }
- LagSpikeCatcher buffSubData = new LagSpikeCatcher();
- GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer);
- buffSubData.end("glBufferSubData BuffStorage");
- }
- else if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING)
- {
- // TODO: Check this half reasonable comment!
- // no stuttering but high GPU usage
- // stores everything in system memory instead of GPU memory
- // making rendering much slower.
- // Unless the user is running integrated graphics,
- // in that case this will actually work better than SUB_DATA.
- LagSpikeCatcher getParm = new LagSpikeCatcher();
- long size = GL32.glGetBufferParameteri(GL32.GL_ARRAY_BUFFER, GL32.GL_BUFFER_SIZE);
- getParm.end("glGetBufferParameteri BuffMapping");
- if (size < uploadBuffer.capacity())
- {
- int newSize = (int) (uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER);
- LagSpikeCatcher buffResize = new LagSpikeCatcher();
- GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
- buffResize.end("glBufferData BuffMapping resize");
- }
- ByteBuffer vboBuffer;
- // map buffer range is better since it can be explicitly unsynchronized
- LagSpikeCatcher buffMap = new LagSpikeCatcher();
- vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(),
- GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT);
- buffMap.end("glMapBufferRange BuffMapping");
- LagSpikeCatcher buffWrite = new LagSpikeCatcher();
- vboBuffer.put(uploadBuffer);
- buffWrite.end("WriteData BuffMapping");
- }
- else if (uploadMethod == GpuUploadMethod.DATA)
- {
- // TODO: Check this nonsense comment!
- // hybrid bufferData //
- // high stutter, low GPU usage
- // But simplest/most compatible
- LagSpikeCatcher buffData = new LagSpikeCatcher();
- GL32.glBufferData(GL32.GL_ARRAY_BUFFER, uploadBuffer, GL32.GL_STATIC_DRAW);
- buffData.end("glBufferData Data");
- }
- else
- {
- // TODO: Check this nonsense comment!
- // hybrid subData/bufferData //
- // less stutter, low GPU usage
- LagSpikeCatcher getParm = new LagSpikeCatcher();
- long size = GL32.glGetBufferParameteri(GL32.GL_ARRAY_BUFFER, GL32.GL_BUFFER_SIZE);
- getParm.end("glGetBufferParameteri SubData");
- if (size < uploadBuffer.capacity())
- {
- int newSize = (int)(uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER);
- LagSpikeCatcher buffResize = new LagSpikeCatcher();
- GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
- buffResize.end("glBufferData SubData resize");
- }
- LagSpikeCatcher buffSubData = new LagSpikeCatcher();
- GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer);
- buffSubData.end("glBufferSubData SubData");
- }
- }
- catch (Exception e)
- {
- ClientApi.LOGGER.error("vboUpload failed: " + e.getClass().getSimpleName());
- e.printStackTrace();
- }
- finally
- {
- LagSpikeCatcher buffUnmap = new LagSpikeCatcher();
- if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING)
- GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER);
- buffUnmap.end("glUnmapBuffer");
-
- LagSpikeCatcher buffUnbind = new LagSpikeCatcher();
- GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
- buffUnbind.end("glBindBuffer 0");
- }
-
- }//if vbo exists and in correct GL context
}//vboUpload
- /** Get the newly created VBOs */
- public VertexBuffersAndOffset getVertexBuffers()
- {
- // don't wait for the lock to open,
- // since this is called on the main render thread
- // TODO: Use atomic swap instead of locks!
- if (bufferLock.tryLock())
+ private boolean swapBuffers() {
+ bufferLock.lock();
+ ClientApi.LOGGER.info("Lod Swap Buffers");
{
+ boolean shouldRegenBuff = true;
try
{
- LodVertexBuffer[][][] tmpVbo = drawableVbos;
+ MovableGridList tmpVbo = drawableVbos;
drawableVbos = buildableVbos;
buildableVbos = tmpVbo;
+
+ //ClientApi.LOGGER.info("Lod Swapped Buffers: "+drawableVbos.toDetailString());
- int[][][] tmpStorage = drawableStorageBufferIds;
- drawableStorageBufferIds = buildableStorageBufferIds;
- buildableStorageBufferIds = tmpStorage;
-
- drawableCenterChunkPosX = buildableCenterBlockPosX;
- drawableCenterChunkPosZ = buildableCenterBlockPosZ;
-
+ int tempX = drawableCenterBlockX;
+ int tempY = drawableCenterBlockY;
+ int tempZ = drawableCenterBlockZ;
+ drawableCenterBlockX = buildableCenterBlockX;
+ drawableCenterBlockY = buildableCenterBlockY;
+ drawableCenterBlockZ = buildableCenterBlockZ;
+ buildableCenterBlockX = tempX;
+ buildableCenterBlockY = tempY;
+ buildableCenterBlockZ = tempZ;
// the vbos have been swapped
switchVbos = false;
+
+ //FIXME: Race condition on the allBuffersRequireReset boolean
+ shouldRegenBuff = frontBufferRequireReset || allBuffersRequireReset;
+ frontBufferRequireReset = allBuffersRequireReset;
+ allBuffersRequireReset = false;
}
catch (Exception e)
{
// this shouldn't normally happen, but just in case it sill prevent deadlock
- ClientApi.LOGGER.info("getVertexBuffers ran into trouble: " + e.getMessage(), e);
+ ClientApi.LOGGER.error("swapBuffers ran into trouble: " + e.getMessage(), e);
}
finally
{
bufferLock.unlock();
}
- }
-
- return new VertexBuffersAndOffset(drawableVbos, drawableStorageBufferIds, drawableCenterChunkPosX, drawableCenterChunkPosZ);
- }
-
- /** A simple container to pass multiple objects back in the getVertexBuffers method. */
- public static class VertexBuffersAndOffset
- {
- public final LodVertexBuffer[][][] vbos;
- public final int[][][] storageBufferIds;
- public int drawableCenterBlockPosX;
- public int drawableCenterBlockPosZ;
-
- public VertexBuffersAndOffset(LodVertexBuffer[][][] newVbos, int[][][] newStorageBufferIds, int newDrawableCenterBlockPosX, int newDrawableCenterBlockPosZ)
- {
- vbos = newVbos;
- storageBufferIds = newStorageBufferIds;
- drawableCenterBlockPosX = newDrawableCenterBlockPosX;
- drawableCenterBlockPosZ = newDrawableCenterBlockPosZ;
+ return shouldRegenBuff;
}
}
- /**
- * If this is true the buildable near and far
- * buffers have been generated and are ready to be
- * sent to the LodRenderer.
- */
- public boolean newBuffersAvailable()
+ /** Get the newly created VBOs */
+ public MovableGridList getFrontBuffers()
{
- return switchVbos;
+ return drawableVbos;
+ }
+ public int getFrontBuffersCenterX()
+ {
+ return drawableCenterBlockX;
+ }
+ public int getFrontBuffersCenterZ()
+ {
+ return drawableCenterBlockZ;
}
}
diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java
index 7ac88a57e..30d5c97c4 100644
--- a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java
+++ b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java
@@ -78,9 +78,9 @@ public class LodDimension
/** stores if the region at the given x and z index needs to be saved to disk */
private volatile boolean[][] isRegionDirty;
/** stores if the region at the given x and z index needs to be regenerated */
- private volatile boolean[][] regenRegionBuffer;
- /** stores if the buffer size at the given x and z index needs to be changed */
- private volatile boolean[][] recreateRegionBuffer;
+ // Use int because I need Tri state:
+ // 0: both buffer good. 1: the displaying buffer good. 2: both buffer bad.
+ private volatile int[][] regenRegionBuffer;
/**
* if true that means there are regions in this dimension
@@ -143,13 +143,14 @@ public class LodDimension
regions = new LodRegion[width][width];
isRegionDirty = new boolean[width][width];
- regenRegionBuffer = new boolean[width][width];
- recreateRegionBuffer = new boolean[width][width];
+ regenRegionBuffer = new int[width][width];
center = new RegionPos(0, 0);
}
+
+ //FIXME: Race condition on this move and other reading regions!
/**
* Move the center of this LodDimension and move all owned
* regions over by the given x and z offset.
@@ -167,9 +168,10 @@ public class LodDimension
if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width)
{
for (int x = 0; x < width; x++)
- for (int z = 0; z < width; z++)
+ for (int z = 0; z < width; z++) {
regions[x][z] = null;
-
+ regenRegionBuffer[x][z] = 0;
+ }
// update the new center
center.x += xOffset;
center.z += zOffset;
@@ -186,10 +188,14 @@ public class LodDimension
{
for (int z = 0; z < width; z++)
{
- if (x + xOffset < width)
+ if (x + xOffset < width) {
regions[x][z] = regions[x + xOffset][z];
- else
+ regenRegionBuffer[x][z] = regenRegionBuffer[x + xOffset][z];
+ }
+ else {
regions[x][z] = null;
+ regenRegionBuffer[x][z] = 0;
+ }
}
}
}
@@ -200,11 +206,14 @@ public class LodDimension
{
for (int z = 0; z < width; z++)
{
- if (x + xOffset >= 0)
+ if (x + xOffset >= 0) {
regions[x][z] = regions[x + xOffset][z];
- else
+ regenRegionBuffer[x][z] = regenRegionBuffer[x + xOffset][z];
+ }
+ else {
regions[x][z] = null;
- }
+ regenRegionBuffer[x][z] = 0;
+ }
}
}
@@ -217,10 +226,14 @@ public class LodDimension
{
for (int z = 0; z < width; z++)
{
- if (z + zOffset < width)
+ if (z + zOffset < width) {
regions[x][z] = regions[x][z + zOffset];
- else
+ regenRegionBuffer[x][z] = regenRegionBuffer[x][z + zOffset];
+ }
+ else {
regions[x][z] = null;
+ regenRegionBuffer[x][z] = 0;
+ }
}
}
}
@@ -231,10 +244,15 @@ public class LodDimension
{
for (int z = width - 1; z >= 0; z--)
{
- if (z + zOffset >= 0)
+ if (z + zOffset >= 0) {
regions[x][z] = regions[x][z + zOffset];
- else
+ regenRegionBuffer[x][z] = regenRegionBuffer[x][z + zOffset];
+ }
+ else {
regions[x][z] = null;
+ regenRegionBuffer[x][z] = 0;
+ }
+ }
}
}
}
@@ -376,7 +394,8 @@ public class LodDimension
if (regions[x][z].getMinDetailLevel() > minAllowedDetailLevel) {
regions[x][z].cutTree(minAllowedDetailLevel);
- recreateRegionBuffer[x][z] = true;
+ regenRegionBuffer[x][z] = 2;
+ regenDimensionBuffers = true;
}
}
});
@@ -430,16 +449,16 @@ public class LodDimension
if (regions[x][z] == null)
regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode, verticalQuality);
- regenRegionBuffer[x][z] = true;
+ regenRegionBuffer[x][z] = 2;
regenDimensionBuffers = true;
- recreateRegionBuffer[x][z] = true;
} else if (region.getMinDetailLevel() > levelToGen) {
// Second case, the region exists at a higher detail level.
// Expand the region by introducing the missing layer
region.growTree(levelToGen);
regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
- recreateRegionBuffer[x][z] = true;
+ regenRegionBuffer[x][z] = 2;
+ regenDimensionBuffers = true;
}
});
};
@@ -476,7 +495,7 @@ public class LodDimension
int zIndex = (regionPosZ - center.z) + halfWidth;
isRegionDirty[xIndex][zIndex] = true;
- regenRegionBuffer[xIndex][zIndex] = true;
+ regenRegionBuffer[xIndex][zIndex] = 2;
regenDimensionBuffers = true;
}
catch (ArrayIndexOutOfBoundsException e)
@@ -518,7 +537,7 @@ public class LodDimension
int zIndex = (regionPosZ - center.z) + halfWidth;
isRegionDirty[xIndex][zIndex] = true;
- regenRegionBuffer[xIndex][zIndex] = true;
+ regenRegionBuffer[xIndex][zIndex] = 2;
regenDimensionBuffers = true;
}
catch (ArrayIndexOutOfBoundsException e)
@@ -538,7 +557,7 @@ public class LodDimension
{
int xIndex = (xRegion - center.x) + halfWidth;
int zIndex = (zRegion - center.z) + halfWidth;
- regenRegionBuffer[xIndex][zIndex] = true;
+ regenRegionBuffer[xIndex][zIndex] = 2;
}
/**
@@ -760,31 +779,35 @@ public class LodDimension
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
-
- LodRegion region = getRegion(detailLevel, posX, posZ);
+
+ int xRegion = LevelPosUtil.getRegion(detailLevel, posX);
+ int zRegion = LevelPosUtil.getRegion(detailLevel, posZ);
+ LodRegion region = getRegion(xRegion, zRegion);
if (region == null)
return;
-
+ markRegionBufferToRegen(xRegion, zRegion);
region.clear(detailLevel, posX, posZ);
}
/**
* Returns if the buffer at the given array index needs
- * to have its buffer regenerated.
+ * to have its buffer regenerated. Also decrease the state by 1
*/
- public boolean doesRegionNeedBufferRegen(int xIndex, int zIndex)
+ public boolean getAndClearRegionNeedBufferRegen(int regionX, int regionZ)
{
- return regenRegionBuffer[xIndex][zIndex] || recreateRegionBuffer[xIndex][zIndex];
- }
-
-
- /**
- * Sets if the buffer at the given array index needs
- * to have its buffer regenerated.
- */
- public void setRegenRegionBufferByArrayIndex(int xIndex, int zIndex, boolean newRegen)
- {
- regenRegionBuffer[xIndex][zIndex] = newRegen;
+ //FIXME: Use actual atomics on regenRegionBuffer
+ //FIXME: Race condition on lodDim move/resize!
+ int xIndex = (regionX - center.x) + halfWidth;
+ int zIndex = (regionZ - center.z) + halfWidth;
+
+ if (xIndex < 0 || xIndex >= width || zIndex < 0 || zIndex >= width)
+ return false;
+ int i = regenRegionBuffer[xIndex][zIndex];
+ if (i > 0) {
+ regenRegionBuffer[xIndex][zIndex]--;
+ return true;
+ }
+ return false;
}
/**
@@ -798,10 +821,13 @@ public class LodDimension
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
-
- LodRegion region = getRegion(detailLevel, posX, posZ);
+
+ int xRegion = LevelPosUtil.getRegion(detailLevel, posX);
+ int zRegion = LevelPosUtil.getRegion(detailLevel, posZ);
+ LodRegion region = getRegion(xRegion, zRegion);
if (region == null)
return;
+ markRegionBufferToRegen(xRegion, zRegion);
region.updateArea(detailLevel, posX, posZ);
}
@@ -882,8 +908,7 @@ public class LodDimension
regions = new LodRegion[width][width];
isRegionDirty = new boolean[width][width];
- regenRegionBuffer = new boolean[width][width];
- recreateRegionBuffer = new boolean[width][width];
+ regenRegionBuffer = new int[width][width];
// populate isRegionDirty
for (int i = 0; i < width; i++)
diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java
index afb88127c..4bf6e94b5 100644
--- a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java
+++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java
@@ -35,13 +35,15 @@ public class LodVertexBuffer implements AutoCloseable
{
public int id;
public int vertexCount;
+ public final boolean isBufferStorage;
+ public long size = 0;
- public LodVertexBuffer()
+ public LodVertexBuffer(boolean isBufferStorage)
{
if (GLProxy.getInstance().getGlContext() == GLProxyContext.NONE)
throw new IllegalStateException("Thread [" +Thread.currentThread().getName() + "] tried to create a [" + LodVertexBuffer.class.getSimpleName() + "] outside a OpenGL contex.");
-
this.id = GL32.glGenBuffers();
+ this.isBufferStorage = isBufferStorage;
}
diff --git a/src/main/java/com/seibel/lod/core/render/LodRenderer.java b/src/main/java/com/seibel/lod/core/render/LodRenderer.java
index 8ee46b275..3d94d14a7 100644
--- a/src/main/java/com/seibel/lod/core/render/LodRenderer.java
+++ b/src/main/java/com/seibel/lod/core/render/LodRenderer.java
@@ -20,15 +20,14 @@
package com.seibel.lod.core.render;
import java.awt.Color;
-import java.util.HashSet;
+import java.time.Duration;
+import java.util.Set;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory;
-import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory.VertexBuffersAndOffset;
-import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.enums.rendering.FogColorMode;
import com.seibel.lod.core.enums.rendering.FogDistance;
@@ -40,9 +39,11 @@ import com.seibel.lod.core.objects.math.Vec3f;
import com.seibel.lod.core.objects.opengl.LodVertexBuffer;
import com.seibel.lod.core.render.objects.LightmapTexture;
import com.seibel.lod.core.util.DetailDistanceUtil;
-import com.seibel.lod.core.util.LevelPosUtil;
+import com.seibel.lod.core.util.GridList;
import com.seibel.lod.core.util.LodUtil;
+import com.seibel.lod.core.util.MovableGridList;
import com.seibel.lod.core.util.SingletonHandler;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
@@ -58,11 +59,40 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
*/
public class LodRenderer
{
+ public static class VanillaRenderedChunksList extends GridList {
+ private static final long serialVersionUID = -5448501880911391315L;
+
+ public final int centerX;
+ public final int centerZ;
+
+ public VanillaRenderedChunksList(int range, int centerX, int centerZ) {
+ super(range);
+ this.centerX = centerX;
+ this.centerZ = centerZ;
+ for (int i=0; i 16000000) { //16 ms
+ ClientApi.LOGGER.info("NOTE: "+source+" took "+Duration.ofNanos(timer)+"!");
+ }
+
+ }
+ }
+
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IReflectionHandler REFLECTION_HANDLER = SingletonHandler.get(IReflectionHandler.class);
-
+
+ public static final int VANILLA_REFRESH_TIMEOUT = 60;
/**
* If true the LODs colors will be replaced with
* a checkerboard, this can be used for debugging.
@@ -75,23 +105,11 @@ public class LodRenderer
/** This is used to generate the buildable buffers */
private final LodBufferBuilderFactory lodBufferBuilderFactory;
- /** Each VertexBuffer represents 1 region */
- private LodVertexBuffer[][][] vbos;
- /**
- * the OpenGL IDs for the vbos of the same indices.
- * These have to be separate because we can't override the
- * buffers in the VBOs (and we don't want to)
- */
- private int[][][] storageBufferIds = null;
-
// The shader program
LodRenderProgram shaderProgram = null;
- private int vbosCenterX = 0;
- private int vbosCenterZ = 0;
-
/** This is used to determine if the LODs should be regenerated */
- private int[] previousPos = new int[] { 0, 0, 0 };
+ private AbstractBlockPosWrapper previousPos = null;
// these variables are used to determine if the buffers should be rebuilt
private int prevRenderDistance = 0;
@@ -115,9 +133,10 @@ public class LodRenderer
* This HashSet contains every chunk that Vanilla Minecraft
* is going to render
*/
- public boolean[][] vanillaRenderedChunks;
- public boolean vanillaRenderedChunksChanged;
- public boolean vanillaRenderedChunksEmptySkip = false;
+ public VanillaRenderedChunksList vanillaRenderedChunks;
+ public int vanillaRenderedChunksCenterX;
+ public int vanillaRenderedChunksCenterZ;
+ public int vanillaRenderedChunksRefreshTimer;
private boolean canVanillaFogBeDisabled = true;
@@ -149,8 +168,6 @@ public class LodRenderer
return;
}
-
-
if (MC_RENDER.playerHasBlindnessEffect())
{
// if the player is blind, don't render LODs,
@@ -175,8 +192,17 @@ public class LodRenderer
// TODO move the buffer regeneration logic into its own class (probably called in the client api instead)
// starting here...
- determineIfLodsShouldRegenerate(lodDim, partialTicks);
+ LagSpikeCatcher updateStatue = new LagSpikeCatcher();
+ updateRegenStatus(lodDim, partialTicks);
+ updateStatue.end("LodDrawSetup:UpdateStatus");
+
+ // FIXME: Currently, we check for last Lod Dimension so that we can trigger a cleanup() if dimension has changed
+ // The better thing to do is to call cleanup() on leaving dimensions in the EventApi, but only for client-side.
+ if (markToCleanup) {
+ markToCleanup = false;
+ cleanup(); // This will unset the isSetupComplete, causing a setup() call.
+ }
//=================//
// create the LODs //
@@ -187,44 +213,32 @@ public class LodRenderer
// 2. we aren't already regenerating the LODs
// 3. we aren't waiting for the build and draw buffers to swap
// (this is to prevent thread conflicts)
- if ((partialRegen || fullRegen) && !lodBufferBuilderFactory.generatingBuffers && !lodBufferBuilderFactory.newBuffersAvailable())
- {
- // generate the LODs on a separate thread to prevent stuttering or freezing
- lodBufferBuilderFactory.generateLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), fullRegen);
-
+ if (lodBufferBuilderFactory.updateAndSwapLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(),
+ MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), partialRegen, fullRegen)) {
// the regen process has been started,
- // it will be done when lodBufferBuilder.newBuffersAvailable()
- // is true
+ // it will be done when lodBufferBuilder.newBuffersAvailable() is true
fullRegen = false;
partialRegen = false;
}
- // TODO move the buffer regeneration logic into its own class (probably called in the client api instead)
- // ...ending here
-
- if (lodBufferBuilderFactory.newBuffersAvailable())
- {
- swapBuffers();
- }
+ // Get the front buffers to draw
+ MovableGridList vbos = lodBufferBuilderFactory.getFrontBuffers();
+ int vbosCenterX = lodBufferBuilderFactory.getFrontBuffersCenterX();
+ int vbosCenterZ = lodBufferBuilderFactory.getFrontBuffersCenterZ();
+ // @Unused
if (vbos == null) {
// There is still no vbos, which means nothing needs to be drawn. So no rendering needed
// (Vbos should be setup by now)
return;
}
-
- // FIXME: Currently, we check for last Lod Dimension so that we can trigger a cleanup() if dimension has changed
- // The better thing to do is to call cleanup() on leaving dimensions in the EventApi, but only for client-side.
- if (markToCleanup) {
- markToCleanup = false;
- cleanup(); // This will unset the isSetupComplete, causing a setup() call.
- }
//===================//
// draw params setup //
//===================//
profiler.push("LOD draw setup");
+ LagSpikeCatcher drawSetup = new LagSpikeCatcher();
/*---------Set GL State--------*/
// Make sure to unbind current VBO so we don't mess up vanilla settings
@@ -256,7 +270,7 @@ public class LodRenderer
/*---------Get required data--------*/
// Get the matrixs for rendering
- Mat4f modelViewMatrix = translateModelViewMatrix(mcModelViewMatrix, partialTicks);
+ Mat4f modelViewMatrix = translateModelViewMatrix(mcModelViewMatrix, partialTicks, vbosCenterX, vbosCenterZ);
int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
int farPlaneBlockDistance;
// required for setupFog and setupProjectionMatrix
@@ -269,7 +283,7 @@ public class LodRenderer
/*---------Fill uniform data--------*/
// Fill the uniform data. Note: GL33.GL_TEXTURE0 == texture bindpoint 0
- shaderProgram.fillUniformData(modelViewMatrix, projectionMatrix, getTranslatedCameraPos(),
+ shaderProgram.fillUniformData(modelViewMatrix, projectionMatrix, getTranslatedCameraPos(vbosCenterX, vbosCenterZ),
MC_RENDER.isFogStateInUnderWater() ? getUnderWaterFogColor(partialTicks) : getFogColor(partialTicks), (int) (MC.getSkyDarken(partialTicks) * 15), 0);
// Previous guy said fog setting may be different from region to region, but the fogSettings never changed... soooooo...
shaderProgram.fillUniformDataForFog(fogSettings, MC_RENDER.isFogStateInUnderWater());
@@ -279,45 +293,44 @@ public class LodRenderer
//===========//
// rendering //
//===========//
-
+ drawSetup.end("LodDrawSetup");
profiler.popPush("LOD draw");
+ LagSpikeCatcher draw = new LagSpikeCatcher();
boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling();
- boolean usingBufferStorage = glProxy.getGpuUploadMethod() == GpuUploadMethod.BUFFER_STORAGE;
// where the center of the buffers is (needed when culling regions)
// render each of the buffers
- for (int x = 0; x < vbos.length; x++)
- {
- for (int z = 0; z < vbos.length; z++)
- {
- //int tempX = LodUtil.convertLevelPos(x + vbosCenterX - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL , LodUtil.BLOCK_DETAIL_LEVEL);
- //int tempY = LodUtil.convertLevelPos(z + vbosCenterZ - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL, LodUtil.BLOCK_DETAIL_LEVEL);
+ int lowRegionX = vbos.getCenterX() - vbos.gridCentreToEdge;
+ int lowRegionZ = vbos.getCenterY() - vbos.gridCentreToEdge;
+ int drawCall = 0;
+ for (int regionX=lowRegionX; regionX
- *
- * For some reason this has to be called after the frame has been rendered,
- * otherwise visual stuttering/rubber banding may happen. I'm not sure why...
- */
- private void swapBuffers()
- {
- // replace the drawable buffers with
- // the newly created buffers from the lodBufferBuilder
- VertexBuffersAndOffset result = lodBufferBuilderFactory.getVertexBuffers();
- vbos = result.vbos;
- storageBufferIds = result.storageBufferIds;
- vbosCenterX = result.drawableCenterBlockPosX;
- vbosCenterZ = result.drawableCenterBlockPosZ;
+ // returns whether anything changed
+ private boolean updateVanillaRenderedChunks(LodDimension lodDim, boolean recreateChunks) {
+ short chunkRenderDistance = (short) MC_RENDER.getRenderDistance();
+ int chunkX = Math.floorDiv(previousPos.getX(), 16);
+ int chunkZ = Math.floorDiv(previousPos.getZ(), 16);
+ // if the player is high enough, draw all LODs
+ if (previousPos.getY() > 256) {
+ vanillaRenderedChunks = new VanillaRenderedChunksList(
+ chunkRenderDistance, chunkX, chunkZ);
+ return true;
+ }
+ VanillaRenderedChunksList chunkList;
+
+ if (recreateChunks) {
+ vanillaRenderedChunks = new VanillaRenderedChunksList(chunkRenderDistance, chunkX, chunkZ);
+ return true;
+ } else {
+ chunkList = vanillaRenderedChunks;
+ chunkX = chunkList.centerX;
+ chunkZ = chunkList.centerZ;
+ chunkRenderDistance = (short) vanillaRenderedChunks.gridCentreToEdge;
+ }
+
+ boolean anyChanged = false;
+ LagSpikeCatcher getChunks = new LagSpikeCatcher();
+ Set chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, previousPos);
+ getChunks.end("LodDrawSetup:UpdateStatus:UpdateVanillaChunks:getChunks");
+ for (AbstractChunkPosWrapper pos : chunkPosToSkip)
+ {
+ int xIndex = (pos.getX() - chunkX) + (chunkRenderDistance + 1);
+ int zIndex = (pos.getZ() - chunkZ) + (chunkRenderDistance + 1);
+
+ // sometimes we are given chunks that are outside the render distance,
+ // This prevents index out of bounds exceptions
+ if (xIndex >= 0 && zIndex >= 0
+ && xIndex < vanillaRenderedChunks.gridSize
+ && zIndex < vanillaRenderedChunks.gridSize)
+ {
+ if (!chunkList.get(chunkList.calculateOffset(xIndex, zIndex)))
+ {
+ chunkList.set(chunkList.calculateOffset(xIndex, zIndex), true);
+ anyChanged = true;
+ lodDim.markRegionBufferToRegen(pos.getRegionX(), pos.getRegionZ());
+ }
+ }
+ }
+ vanillaRenderedChunks = chunkList;
+ return anyChanged;
}
- /** Determines if the LODs should have a fullRegen or partialRegen */
- private void determineIfLodsShouldRegenerate(LodDimension lodDim, float partialTicks)
- {
+ private void updateRegenStatus(LodDimension lodDim, float partialTicks) {
short chunkRenderDistance = (short) MC_RENDER.getRenderDistance();
- int vanillaRenderedChunksWidth = chunkRenderDistance * 2 + 2;
-
- //=============//
- // full regens //
- //=============//
-
- // check if the view distance changed
+ long newTime = System.currentTimeMillis();
+ AbstractBlockPosWrapper newPos = MC.getPlayerBlockPos();
+ boolean shouldUpdateChunks = false;
+ boolean posUpdated = false;
+ boolean tryPartialGen = false;
+ boolean tryFullGen = false;
+
+ // check if the view distance or config changed
if (ApiShared.previousLodRenderDistance != CONFIG.client().graphics().quality().getLodChunkRenderDistance()
|| chunkRenderDistance != prevRenderDistance
|| prevFogDistance != CONFIG.client().graphics().fogQuality().getFogDistance())
{
-
- vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
- DetailDistanceUtil.updateSettings();
- fullRegen = true;
- previousPos = LevelPosUtil.createLevelPos((byte) 4, MC.getPlayerChunkPos().getZ(), MC.getPlayerChunkPos().getZ());
+ DetailDistanceUtil.updateSettings(); // FIXME: This should NOT be here!
prevFogDistance = CONFIG.client().graphics().fogQuality().getFogDistance();
prevRenderDistance = chunkRenderDistance;
- }
-
- // did the user change the debug setting?
- if (CONFIG.client().advanced().debugging().getDebugMode() != previousDebugMode)
- {
+ tryFullGen = true;
+ } else if (CONFIG.client().advanced().debugging().getDebugMode() != previousDebugMode)
+ { // did the user change the debug setting?
previousDebugMode = CONFIG.client().advanced().debugging().getDebugMode();
- fullRegen = true;
+ tryFullGen = true;
}
-
- long newTime = System.currentTimeMillis();
-
// check if the player has moved
- if (newTime - prevPlayerPosTime > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveTimeout)
- {
- if (LevelPosUtil.getDetailLevel(previousPos) == 0
- || Math.abs(MC.getPlayerChunkPos().getX() - LevelPosUtil.getPosX(previousPos)) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance
- || Math.abs(MC.getPlayerChunkPos().getZ() - LevelPosUtil.getPosZ(previousPos)) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance)
+ if (newTime - prevPlayerPosTime > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveTimeout) {
+ if (previousPos == null
+ || Math.abs(newPos.getX() - previousPos.getX()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16
+ || Math.abs(newPos.getZ() - previousPos.getZ()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16)
{
- vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
- fullRegen = true;
- previousPos = LevelPosUtil.createLevelPos((byte) 4, MC.getPlayerChunkPos().getX(), MC.getPlayerChunkPos().getZ());
+ tryPartialGen = true;
+ previousPos = newPos;
+ posUpdated = true;
}
prevPlayerPosTime = newTime;
}
-
- //================//
- // partial regens //
- //================//
-
+
// check if the vanilla rendered chunks changed
if (newTime - prevVanillaChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().renderedChunkTimeout)
{
- if (vanillaRenderedChunksChanged)
- {
- partialRegen = true;
- vanillaRenderedChunksChanged = false;
- }
+ shouldUpdateChunks = true;
prevVanillaChunkTime = newTime;
}
-
+
// check if there is any newly generated terrain to show
if (newTime - prevChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().chunkChangeTimeout)
{
+ tryPartialGen = true;
+ prevChunkTime = newTime;
+ }
+
+
+ if (tryFullGen && !posUpdated) {
+ previousPos = newPos;
+ posUpdated = true;
+ }
+ shouldUpdateChunks |= posUpdated;
+ if (shouldUpdateChunks) {
+ tryPartialGen |= updateVanillaRenderedChunks(lodDim, posUpdated);
+ }
+
+ if (tryFullGen) {
+ fullRegen = true;
+ lodDim.regenDimensionBuffers = false;
+ } else if (tryPartialGen) {
if (lodDim.regenDimensionBuffers)
{
partialRegen = true;
lodDim.regenDimensionBuffers = false;
}
- prevChunkTime = newTime;
}
-
- //==============//
- // LOD skipping //
- //==============//
-
- // determine which LODs should not be rendered close to the player
- HashSet chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, MC.getPlayerBlockPos());
- int xIndex;
- int zIndex;
- for (AbstractChunkPosWrapper pos : chunkPosToSkip)
- {
- vanillaRenderedChunksEmptySkip = false;
-
- xIndex = (pos.getX() - MC.getPlayerChunkPos().getX()) + (chunkRenderDistance + 1);
- zIndex = (pos.getZ() - MC.getPlayerChunkPos().getZ()) + (chunkRenderDistance + 1);
-
- // sometimes we are given chunks that are outside the render distance,
- // This prevents index out of bounds exceptions
- if (xIndex >= 0 && zIndex >= 0
- && xIndex < vanillaRenderedChunks.length
- && zIndex < vanillaRenderedChunks.length)
- {
- if (!vanillaRenderedChunks[xIndex][zIndex])
- {
- vanillaRenderedChunks[xIndex][zIndex] = true;
- vanillaRenderedChunksChanged = true;
- lodDim.markRegionBufferToRegen(pos.getRegionX(), pos.getRegionZ());
- }
- }
- }
-
-
- // if the player is high enough, draw all LODs
- if (chunkPosToSkip.isEmpty() && MC.getPlayerBlockPos().getY() > 256 && !vanillaRenderedChunksEmptySkip)
- {
- vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
- vanillaRenderedChunksChanged = true;
- vanillaRenderedChunksEmptySkip = true;
- }
-
- vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
}
}
diff --git a/src/main/java/com/seibel/lod/core/render/RenderUtil.java b/src/main/java/com/seibel/lod/core/render/RenderUtil.java
index abb131123..5bac64cbf 100644
--- a/src/main/java/com/seibel/lod/core/render/RenderUtil.java
+++ b/src/main/java/com/seibel/lod/core/render/RenderUtil.java
@@ -85,23 +85,20 @@ public class RenderUtil
* Returns true if one of the region's 4 corners is in front
* of the camera.
*/
- public static boolean isRegionInViewFrustum(AbstractBlockPosWrapper playerBlockPos, Vec3f cameraDir, int vboCenterPosX, int vboCenterPosZ)
+ public static boolean isRegionInViewFrustum(AbstractBlockPosWrapper playerBlockPos, Vec3f cameraDir, int vboRegionX, int vboRegionZ)
{
// convert the vbo position into a direction vector
// starting from the player's position
- Vec3f vboVec = new Vec3f(vboCenterPosX, 0, vboCenterPosZ);
+ Vec3f vboVec = new Vec3f(vboRegionX * LodUtil.REGION_WIDTH, 0, vboRegionZ * LodUtil.REGION_WIDTH);
Vec3f playerVec = new Vec3f(playerBlockPos.getX(), playerBlockPos.getY(), playerBlockPos.getZ());
vboVec.subtract(playerVec);
-
- int halfRegionWidth = LodUtil.REGION_WIDTH / 2;
-
// calculate the 4 corners
- Vec3f vboSeVec = new Vec3f(vboVec.x + halfRegionWidth, vboVec.y, vboVec.z + halfRegionWidth);
- Vec3f vboSwVec = new Vec3f(vboVec.x - halfRegionWidth, vboVec.y, vboVec.z + halfRegionWidth);
- Vec3f vboNwVec = new Vec3f(vboVec.x - halfRegionWidth, vboVec.y, vboVec.z - halfRegionWidth);
- Vec3f vboNeVec = new Vec3f(vboVec.x + halfRegionWidth, vboVec.y, vboVec.z - halfRegionWidth);
+ Vec3f vboSeVec = new Vec3f(vboVec.x + LodUtil.REGION_WIDTH, vboVec.y, vboVec.z + LodUtil.REGION_WIDTH);
+ Vec3f vboSwVec = new Vec3f(vboVec.x , vboVec.y, vboVec.z + LodUtil.REGION_WIDTH);
+ Vec3f vboNwVec = new Vec3f(vboVec.x , vboVec.y, vboVec.z);
+ Vec3f vboNeVec = new Vec3f(vboVec.x + LodUtil.REGION_WIDTH, vboVec.y, vboVec.z);
// if any corner is visible, this region should be rendered
return isNormalizedVectorInViewFrustum(vboSeVec, cameraDir) ||
diff --git a/src/main/java/com/seibel/lod/core/util/GridList.java b/src/main/java/com/seibel/lod/core/util/GridList.java
new file mode 100644
index 000000000..8517aef2c
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/util/GridList.java
@@ -0,0 +1,76 @@
+package com.seibel.lod.core.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GridList extends ArrayList implements List {
+
+ public static class Pos {
+ public int x;
+ public int y;
+
+ public Pos(int xx, int yy) {
+ x = xx;
+ y = yy;
+ }
+ }
+
+ private static final long serialVersionUID = 1585978374811888116L;
+ public final int gridCentreToEdge;
+ public final int gridSize;
+
+ public GridList(int gridCentreToEdge) {
+ super((gridCentreToEdge * 2 + 1) * (gridCentreToEdge * 2 + 1));
+ gridSize = gridCentreToEdge * 2 + 1;
+ this.gridCentreToEdge = gridCentreToEdge;
+ }
+
+ public final T getOffsetOf(int index, int x, int y) {
+ return get(index + x + y * gridSize);
+ }
+
+ public final int offsetOf(int index, int x, int y) {
+ return index + x + y * gridSize;
+ }
+
+ public final Pos posOf(int index) {
+ return new Pos(index % gridSize, index / gridSize);
+ }
+
+ public final int calculateOffset(int x, int y) {
+ return x + y * gridSize;
+ }
+
+ public final GridList subGrid(int gridCentreToEdge) {
+ int centreIndex = size() / 2;
+ GridList subGrid = new GridList(gridCentreToEdge);
+ for (int oy = -gridCentreToEdge; oy <= gridCentreToEdge; oy++) {
+ int begin = offsetOf(centreIndex, -gridCentreToEdge, oy);
+ int end = offsetOf(centreIndex, gridCentreToEdge, oy);
+ subGrid.addAll(this.subList(begin, end + 1));
+ }
+ // System.out.println("========================================\n"+
+ // this.toDetailString() + "\nTOOOOOOOOOOOOO\n"+subGrid.toDetailString()+
+ // "==========================================\n");
+ return subGrid;
+ }
+
+ @Override
+ public String toString() {
+ return "GridList " + gridSize + "*" + gridSize + "[" + size() + "]";
+ }
+
+ public String toDetailString() {
+ StringBuilder str = new StringBuilder("\n");
+ int i = 0;
+ for (T t : this) {
+ str.append(t.toString());
+ str.append(", ");
+ i++;
+ if (i % gridSize == 0) {
+ str.append("\n");
+ }
+ }
+ return str.toString();
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/util/LodUtil.java b/src/main/java/com/seibel/lod/core/util/LodUtil.java
index c13d78dcd..d14b41932 100644
--- a/src/main/java/com/seibel/lod/core/util/LodUtil.java
+++ b/src/main/java/com/seibel/lod/core/util/LodUtil.java
@@ -181,12 +181,6 @@ public class LodUtil
return new RegionPos(relativePosX, relativePosZ);
}
- /** Convert a 2D absolute position into a quad tree relative position. */
- public static int convertLevelPos(int pos, int currentDetailLevel, int targetDetailLevel)
- {
- return pos / (1 << (targetDetailLevel - currentDetailLevel));
- }
-
/**
* If on single player this will return the name of the user's
diff --git a/src/main/java/com/seibel/lod/core/util/MovableGridList.java b/src/main/java/com/seibel/lod/core/util/MovableGridList.java
new file mode 100644
index 000000000..7a2d75968
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/util/MovableGridList.java
@@ -0,0 +1,168 @@
+package com.seibel.lod.core.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/*Layout:
+ * 0,1,2,
+ * 3,4,5,
+ * 6,7,8
+ */
+
+public class MovableGridList extends ArrayList implements List {
+
+ private static final long serialVersionUID = 5366261085254591277L;
+
+ public static class Pos {
+ public int x;
+ public int y;
+
+ public Pos(int xx, int yy) {
+ x = xx;
+ y = yy;
+ }
+ }
+
+ private int centerX;
+ private int centerY;
+
+ public final int gridCentreToEdge;
+ public final int gridSize;
+
+ public MovableGridList(int gridCentreToEdge, int centerX, int centerY) {
+ super((gridCentreToEdge * 2 + 1) * (gridCentreToEdge * 2 + 1));
+ gridSize = gridCentreToEdge * 2 + 1;
+ this.gridCentreToEdge = gridCentreToEdge;
+ this.centerX = centerX;
+ this.centerY = centerY;
+ clear();
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ super.ensureCapacity(gridSize*gridSize);
+ for (int i=0; i=gridSize || y<0 || y>=gridSize) return null;
+ return super.get(x + y * gridSize);
+ }
+ private final boolean _setDirect(int x, int y, T t) {
+ if (x<0 || x>=gridSize || y<0 || y>=gridSize) return false;
+ super.set(x + y * gridSize, t);
+ return true;
+ }
+
+ public void move(int newCenterX, int newCenterY) {
+ if (centerX == newCenterX && centerY == newCenterY) return;
+ int deltaX = newCenterX - centerX;
+ int deltaY = newCenterY - centerY;
+
+ // if the x or z offset is equal to or greater than
+ // the total width, just delete the current data
+ // and update the centerX and/or centerZ
+ if (Math.abs(deltaX) >= gridSize || Math.abs(deltaY) >= gridSize)
+ {
+ clear();
+ // update the new center
+ centerX = newCenterX;
+ centerY = newCenterY;
+ return;
+ }
+
+ // X
+ if (deltaX >= 0 && deltaY >= 0)
+ {
+ // move everything over to the left-up (as the center moves to the right-down)
+ for (int x = 0; x < gridSize; x++)
+ {
+ for (int y = 0; y < gridSize; y++)
+ {
+ _setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
+ }
+ }
+ }
+ else if (deltaX < 0 && deltaY >= 0)
+ {
+ // move everything over to the right-up (as the center moves to the left-down)
+ for (int x = gridSize - 1; x >= 0; x--)
+ {
+ for (int y = 0; y < gridSize; y++)
+ {
+ _setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
+ }
+ }
+ }
+ else if (deltaX >= 0 && deltaY < 0)
+ {
+ // move everything over to the left-down (as the center moves to the right-up)
+ for (int x = 0; x < gridSize; x++)
+ {
+ for (int y = gridSize - 1; y >= 0; y--)
+ {
+ _setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
+ }
+ }
+ }
+ else //if (deltaX < 0 && deltaY < 0)
+ {
+ // move everything over to the right-down (as the center moves to the left-up)
+ for (int x = gridSize - 1; x >= 0; x--)
+ {
+ for (int y = gridSize - 1; y >= 0; y--)
+ {
+ _setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
+ }
+ }
+ }
+ centerX = newCenterX;
+ centerY = newCenterY;
+ }
+
+
+ // TODO: This is unused but may be useful later on.
+ /*
+ public final MovableGridList subGrid(int gridCentreToEdge, int newCenterX, int newCenterY) {
+ }*/
+
+ @Override
+ public String toString() {
+ return "MovableGridList[" + centerX + "," + centerY + "] " + gridSize + "*" + gridSize + "[" + size() + "]";
+ }
+
+ public String toDetailString() {
+ StringBuilder str = new StringBuilder("\n");
+ int i = 0;
+ for (T t : this) {
+
+ str.append(t!=null ? t.toString() : "NULL");
+ str.append(", ");
+ i++;
+ if (i % gridSize == 0) {
+ str.append("\n");
+ }
+ }
+ return str.toString();
+ }
+}