Fix direct memory leak and remove config for GpuUpload

This commit is contained in:
James Seibel
2024-08-07 07:29:52 -05:00
parent a8df13fdd2
commit 90fdfbbe61
11 changed files with 161 additions and 626 deletions
@@ -23,13 +23,12 @@ package com.seibel.distanthorizons.api.enums.config;
* AUTO, <br>
* BUFFER_STORAGE, <br>
* SUB_DATA, <br>
* BUFFER_MAPPING, <br>
* DATA <br>
*
* @author Leetom
* @author James Seibel
* @version 2024-4-6
* @since API 2.0.0
* @since API 3.0.0
*/
public enum EDhApiGpuUploadMethod
{
@@ -49,7 +48,10 @@ public enum EDhApiGpuUploadMethod
* May end up storing buffers in System memory. <br>
* Fast rending if in GPU memory, slow if in system memory, <br>
* but won't stutter when uploading.
*
* @deprecated not currently supported
*/
@Deprecated
BUFFER_MAPPING(true, false),
/** Fast rendering but may stutter when uploading. */
@@ -36,7 +36,6 @@ public interface IDhApiConfig
IDhApiWorldGenerationConfig worldGenerator();
IDhApiMultiplayerConfig multiplayer();
IDhApiMultiThreadingConfig multiThreading();
IDhApiGpuBuffersConfig gpuBuffers();
// note: DON'T add the Auto Updater to this API. We only want the user's to have the ability to control when things are downloaded to their machines.
//IDhApiLoggingConfig logging(); // TODO implement
IDhApiDebuggingConfig debugging();
@@ -1,48 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.interfaces.config.client;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
/**
* Distant Horizons' OpenGL buffer configuration.
*
* @author James Seibel
* @version 2023-6-14
* @since API 1.0.0
*/
public interface IDhApiGpuBuffersConfig extends IDhApiConfigGroup
{
/** Defines how geometry data is uploaded to the GPU. */
IDhApiConfigValue<EDhApiGpuUploadMethod> gpuUploadMethod();
/**
* Defines how long we should wait after uploading one
* Megabyte of geometry data to the GPU before uploading
* the next Megabyte of data. <br>
* This can be set to a non-zero number to reduce stuttering caused by
* uploading buffers to the GPU.
*/
IDhApiConfigValue<Integer> gpuUploadPerMegabyteInMilliseconds();
}
@@ -42,8 +42,6 @@ public class DhApiConfig implements IDhApiConfig
@Override
public IDhApiMultiThreadingConfig multiThreading() { return DhApiMultiThreadingConfig.INSTANCE; }
@Override
public IDhApiGpuBuffersConfig gpuBuffers() { return DhApiGpuBuffersConfig.INSTANCE; }
@Override
public IDhApiDebuggingConfig debugging() { return DhApiDebuggingConfig.INSTANCE; }
}
@@ -1,42 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiGpuBuffersConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
public class DhApiGpuBuffersConfig implements IDhApiGpuBuffersConfig
{
public static DhApiGpuBuffersConfig INSTANCE = new DhApiGpuBuffersConfig();
private DhApiGpuBuffersConfig() { }
public IDhApiConfigValue<EDhApiGpuUploadMethod> gpuUploadMethod()
{ return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadMethod); }
public IDhApiConfigValue<Integer> gpuUploadPerMegabyteInMilliseconds()
{ return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds); }
}
@@ -125,7 +125,6 @@ public class Config
public static ConfigCategory multiplayer = new ConfigCategory.Builder().set(Multiplayer.class).build();
public static ConfigCategory lodBuilding = new ConfigCategory.Builder().set(LodBuilding.class).build();
public static ConfigCategory multiThreading = new ConfigCategory.Builder().set(MultiThreading.class).build();
public static ConfigCategory buffers = new ConfigCategory.Builder().set(GpuBuffers.class).build();
public static ConfigCategory autoUpdater = new ConfigCategory.Builder().set(AutoUpdater.class).build();
public static ConfigCategory logging = new ConfigCategory.Builder().set(Logging.class).build();
@@ -1072,53 +1071,6 @@ public class Config
}
public static class GpuBuffers
{
public static ConfigEntry<EDhApiGpuUploadMethod> gpuUploadMethod = new ConfigEntry.Builder<EDhApiGpuUploadMethod>()
.set(EDhApiGpuUploadMethod.AUTO)
.comment(""
+ "What method should be used to upload geometry to the GPU? \n"
+ "\n"
+ EDhApiGpuUploadMethod.AUTO + ": Picks the best option based on the GPU you have. \n"
+ "\n"
+ EDhApiGpuUploadMethod.BUFFER_STORAGE + ": Default if OpenGL 4.5 is supported. \n"
+ " Fast rendering, no stuttering. \n"
+ "\n"
+ EDhApiGpuUploadMethod.SUB_DATA + ": Backup option for NVIDIA. \n"
+ " Fast rendering but may stutter when uploading. \n"
+ "\n"
+ EDhApiGpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. \n"
+ " Generally the best option for integrated GPUs. \n"
+ " Default option for AMD/Intel if OpenGL 4.5 isn't supported. \n"
+ " May end up storing buffers in System memory. \n"
+ " Fast rendering if in GPU memory, slow if in system memory, \n"
+ " but won't stutter when uploading. \n"
+ "\n"
+ EDhApiGpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n"
+ " Backup option for AMD/Intel. \n"
+ " Fast rendering but may stutter when uploading. \n"
+ "\n"
+ "If you don't see any difference when changing these settings, \n"
+ "or the world looks corrupted: restart your game."
+ "")
.build();
public static ConfigEntry<Integer> gpuUploadPerMegabyteInMilliseconds = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0, 0, 50)
.comment(""
+ "How long should a buffer wait per Megabyte of data uploaded? \n"
+ "Helpful resource for frame times: https://fpstoms.com \n"
+ "\n"
+ "Longer times may reduce stuttering but will make LODs \n"
+ "transition and load slower. Change this to [0] for no timeout. \n"
+ "\n"
+ "NOTE:\n"
+ "Before changing this config, try changing the \"GPU Upload method\" first. \n"
+ "")
.build();
}
public static class AutoUpdater
{
public static ConfigEntry<Boolean> enableAutoUpdater = new ConfigEntry.Builder<Boolean>()
@@ -36,7 +36,7 @@ import org.apache.logging.log4j.Logger;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.concurrent.*;
/**
@@ -101,7 +101,7 @@ public class ColumnRenderBuffer implements AutoCloseable
{
try
{
this.uploadBuffersUsingUploadMethod(builder, gpuUploadMethod);
this.uploadBuffers(builder, gpuUploadMethod);
uploadFuture.complete(null);
}
catch (InterruptedException e)
@@ -127,72 +127,46 @@ public class ColumnRenderBuffer implements AutoCloseable
//LOGGER.warn("Error uploading builder ["+builder+"] synchronously. Error: "+e.getMessage(), e);
}
}
private void uploadBuffersUsingUploadMethod(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException
private void uploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException
{
if (gpuUploadMethod.useEarlyMapping)
{
this.uploadBuffersMapped(builder, gpuUploadMethod);
}
else
{
this.uploadBuffersDirect(builder, gpuUploadMethod);
}
// uploading mapped buffers used to be done here,
// however due to a memory leak and complication with the previous code,
// now we only allow direct uploading.
// (There's also insufficient data to state whether mapped buffers are necessary
// for DH to upload without stuttering the main thread)
this.vbos = makeAndUploadBuffers(builder, method, this.vbos, builder.makeOpaqueVertexBuffers());
this.vbosTransparent = makeAndUploadBuffers(builder, method, this.vbosTransparent, builder.makeTransparentVertexBuffers());
this.buffersUploaded = true;
}
private void uploadBuffersMapped(LodQuadBuilder builder, EDhApiGpuUploadMethod method)
/** This resizes and returns the vbo array if necessary based on the amount of data needed for this area. */
private static GLVertexBuffer[] makeAndUploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method, GLVertexBuffer[] vbos, ArrayList<ByteBuffer> buffers) throws InterruptedException
{
// opaque vbos //
this.vbos = resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
for (int i = 0; i < this.vbos.length; i++)
try
{
if (this.vbos[i] == null)
vbos = resizeBuffer(vbos, buffers.size());
uploadBuffersDirect(vbos, buffers, method);
}
finally
{
// all the buffers must be manually freed to prevent memory leaks
if (buffers != null)
{
this.vbos[i] = new GLVertexBuffer(method.useBufferStorage);
for (ByteBuffer buffer : buffers)
{
MemoryUtil.memFree(buffer);
}
}
}
LodQuadBuilder.BufferFiller func = builder.makeOpaqueBufferFiller(method);
for (GLVertexBuffer vbo : this.vbos)
{
func.fill(vbo);
}
// transparent vbos //
this.vbosTransparent = resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
for (int i = 0; i < this.vbosTransparent.length; i++)
{
if (this.vbosTransparent[i] == null)
{
this.vbosTransparent[i] = new GLVertexBuffer(method.useBufferStorage);
}
}
LodQuadBuilder.BufferFiller transparentFillerFunc = builder.makeTransparentBufferFiller(method);
for (GLVertexBuffer vbo : this.vbosTransparent)
{
transparentFillerFunc.fill(vbo);
}
// return the array in case it was resized
return vbos;
}
private void uploadBuffersDirect(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, ArrayList<ByteBuffer> byteBuffers, EDhApiGpuUploadMethod method) throws InterruptedException
{
this.vbos = resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
uploadBuffersDirect(this.vbos, builder.makeOpaqueVertexBuffers(), method);
this.vbosTransparent = resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
uploadBuffersDirect(this.vbosTransparent, builder.makeTransparentVertexBuffers(), method);
}
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, Iterator<ByteBuffer> iter, EDhApiGpuUploadMethod method) throws InterruptedException
{
long remainingMS = 0;
long MBPerMS = Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds.get();
int vboIndex = 0;
while (iter.hasNext())
for (int i = 0; i < byteBuffers.size(); i++)
{
if (vboIndex >= vbos.length)
{
@@ -208,41 +182,19 @@ public class ColumnRenderBuffer implements AutoCloseable
GLVertexBuffer vbo = vbos[vboIndex];
ByteBuffer bb = iter.next();
int size = bb.limit() - bb.position();
ByteBuffer buffer = byteBuffers.get(i);
int size = buffer.limit() - buffer.position();
try
{
vbo.bind();
vbo.uploadBuffer(bb, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
vbo.uploadBuffer(buffer, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
}
catch (Exception e)
{
vbos[vboIndex] = null;
vbo.close();
LOGGER.error("Failed to upload buffer: ", e);
}
finally
{
MemoryUtil.memFree(bb);
}
if (MBPerMS > 0)
{
// upload buffers over an extended period of time
// to hopefully prevent stuttering.
remainingMS += size * MBPerMS;
if (remainingMS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS))
{
if (remainingMS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
{
remainingMS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
}
Thread.sleep(remainingMS / 1000000, (int) (remainingMS % 1000000));
remainingMS = 0;
}
}
vboIndex++;
@@ -206,10 +206,124 @@ public class LodQuadBuilder
//=================//
// data finalizing //
//=================//
/** runs any final data cleanup, merging, etc. */
public void finalizeData() { this.mergeQuads(); }
/** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads()
{
long mergeCount = 0; // can be used for debugging
long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
if (preQuadsCount <= 1)
{
return;
}
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
}
// only run the second merge if the face is the top or bottom
if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal())
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
}
}
}
//long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
//LOGGER.trace("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads");
}
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
private static long mergeQuadsInternal(ArrayList<BufferQuad>[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection)
{
if (list[directionIndex].size() <= 1)
return 0;
list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection));
long mergeCount = 0;
ListIterator<BufferQuad> iter = list[directionIndex].listIterator();
BufferQuad currentQuad = iter.next();
while (iter.hasNext())
{
BufferQuad nextQuad = iter.next();
if (currentQuad.tryMerge(nextQuad, mergeDirection))
{
// merge successful, attempt to merge the next quad
mergeCount++;
iter.set(null);
}
else
{
// merge fail, move on to the next quad
currentQuad = nextQuad;
}
}
list[directionIndex].removeIf(Objects::isNull);
return mergeCount;
}
//==============//
// add vertices //
// buffer setup //
//==============//
public ArrayList<ByteBuffer> makeOpaqueVertexBuffers() { return this.makeVertexBuffers(this.opaqueQuads); }
public ArrayList<ByteBuffer> makeTransparentVertexBuffers() { return this.makeVertexBuffers(this.transparentQuads); }
private ArrayList<ByteBuffer> makeVertexBuffers(ArrayList<BufferQuad>[] quadList)
{
ArrayList<ByteBuffer> byteBufferList = new ArrayList<>(3);
ByteBuffer buffer = null;
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
// ignore empty directions
if (quadList[directionIndex].isEmpty())
{
continue;
}
// put all the quads in this direction into the buffer
for (int quadIndex = 0; quadIndex < quadList[directionIndex].size(); quadIndex++)
{
// if this is the first iteration or the buffer is full,
// create a new buffer
if (buffer == null || !buffer.hasRemaining())
{
buffer = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER);
byteBufferList.add(buffer);
}
this.putQuad(buffer, quadList[directionIndex].get(quadIndex));
}
}
// rewind all the buffers so they can be read from
for (int i = 0; i < byteBufferList.size(); i++)
{
buffer = byteBufferList.get(i);
buffer.limit(buffer.position());
buffer.rewind();
}
return byteBufferList;
}
private void putQuad(ByteBuffer bb, BufferQuad quad)
{
int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()];
@@ -267,10 +381,10 @@ public class LodQuadBuilder
if (quad.direction.getAxis().isHorizontal() || quad.direction == EDhDirection.DOWN)
{
if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT
// if we want the color to fade, only apply the dirt color to the bottom vertices
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
// always render the bottom as dirt
|| quad.direction == EDhDirection.DOWN)
// if we want the color to fade, only apply the dirt color to the bottom vertices
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
// always render the bottom as dirt
|| quad.direction == EDhDirection.DOWN)
{
// for horizontal and bottom faces of grass blocks, use the dirt color to
// prevent green cliff walls
@@ -278,7 +392,7 @@ public class LodQuadBuilder
color = ColorUtil.applyShade(color, MC.getShade(quad.direction));
}
}
}
}
}
}
@@ -292,7 +406,6 @@ public class LodQuadBuilder
mx, my, mz);
}
}
private void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte normalIndex, byte irisBlockMaterialId, byte skylight, byte blocklight, int mx, int my, int mz)
{
skylight %= 16;
@@ -333,387 +446,6 @@ public class LodQuadBuilder
//=================//
// data finalizing //
//=================//
/** runs any final data cleanup, merging, etc. */
public void finalizeData() { this.mergeQuads(); }
/** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads()
{
long mergeCount = 0;
long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
if (preQuadsCount <= 1)
{
return;
}
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
}
// only run the second merge if the face is the top or bottom
if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal())
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
}
}
}
long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
LOGGER.debug("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads");
}
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
private static long mergeQuadsInternal(ArrayList<BufferQuad>[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection)
{
if (list[directionIndex].size() <= 1)
return 0;
list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection));
long mergeCount = 0;
ListIterator<BufferQuad> iter = list[directionIndex].listIterator();
BufferQuad currentQuad = iter.next();
while (iter.hasNext())
{
BufferQuad nextQuad = iter.next();
if (currentQuad.tryMerge(nextQuad, mergeDirection))
{
// merge successful, attempt to merge the next quad
mergeCount++;
iter.set(null);
}
else
{
// merge fail, move on to the next quad
currentQuad = nextQuad;
}
}
list[directionIndex].removeIf(Objects::isNull);
return mergeCount;
}
//==============//
// buffer setup //
//==============//
public Iterator<ByteBuffer> makeOpaqueVertexBuffers()
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER);
int dir = this.skipEmpty(0);
int quad = 0;
private int skipEmpty(int d)
{
while (d < 6 && opaqueQuads[d].isEmpty())
{
d++;
}
return d;
}
@Override
public boolean hasNext()
{
return dir < 6;
}
@Override
public ByteBuffer next()
{
if (dir >= 6)
{
return null;
}
bb.clear();
bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER);
while (bb.hasRemaining() && dir < 6)
{
writeData();
}
bb.limit(bb.position());
bb.rewind();
return bb;
}
private void writeData()
{
int i = quad;
for (; i < opaqueQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, opaqueQuads[dir].get(i));
}
if (i >= opaqueQuads[dir].size())
{
quad = 0;
dir++;
dir = skipEmpty(dir);
}
else
{
quad = i;
}
}
};
}
public Iterator<ByteBuffer> makeTransparentVertexBuffers()
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER);
int directionIndex = this.skipEmptyDirectionIndices(0);
int quad = 0;
private int skipEmptyDirectionIndices(int directionIndex)
{
while (directionIndex < 6 &&
(LodQuadBuilder.this.transparentQuads[directionIndex] == null
|| LodQuadBuilder.this.transparentQuads[directionIndex].isEmpty()))
{
directionIndex++;
}
return directionIndex;
}
@Override
public boolean hasNext() { return this.directionIndex < 6; }
@Override
public ByteBuffer next()
{
if (this.directionIndex >= 6)
{
return null;
}
this.bb.clear();
this.bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER);
while (this.bb.hasRemaining() && this.directionIndex < 6)
{
this.writeData();
}
this.bb.limit(this.bb.position());
this.bb.rewind();
return this.bb;
}
private void writeData()
{
int i = this.quad;
for (; i < LodQuadBuilder.this.transparentQuads[this.directionIndex].size(); i++)
{
if (!this.bb.hasRemaining())
{
break;
}
putQuad(this.bb, LodQuadBuilder.this.transparentQuads[this.directionIndex].get(i));
}
if (i >= LodQuadBuilder.this.transparentQuads[this.directionIndex].size())
{
this.quad = 0;
this.directionIndex++;
this.directionIndex = this.skipEmptyDirectionIndices(this.directionIndex);
}
else
{
this.quad = i;
}
}
};
}
public interface BufferFiller
{
/** If true: more data needs to be filled */
boolean fill(GLVertexBuffer vbo);
}
public BufferFiller makeOpaqueBufferFiller(EDhApiGpuUploadMethod method)
{
return new BufferFiller()
{
int dir = 0;
int quad = 0;
public boolean fill(GLVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.setVertexCount(0);
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method,
ColumnRenderBuffer.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer();
vbo.setVertexCount(numOfQuads * 4);
return dir < 6;
}
private int _countRemainingQuads()
{
int a = opaqueQuads[dir].size() - quad;
for (int i = dir + 1; i < opaqueQuads.length; i++)
{
a += opaqueQuads[i].size();
}
return a;
}
private void writeData(ByteBuffer bb)
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < opaqueQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, opaqueQuads[dir].get(i));
}
if (i >= opaqueQuads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && opaqueQuads[dir].isEmpty())
{
dir++;
}
}
else
{
quad = i;
}
}
};
}
public BufferFiller makeTransparentBufferFiller(EDhApiGpuUploadMethod method)
{
return new BufferFiller()
{
int dir = 0;
int quad = 0;
public boolean fill(GLVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.setVertexCount(0);
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method,
ColumnRenderBuffer.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer();
vbo.setVertexCount(numOfQuads * 4);
return dir < 6;
}
private int _countRemainingQuads()
{
int a = transparentQuads[dir].size() - quad;
for (int i = dir + 1; i < transparentQuads.length; i++)
{
a += transparentQuads[i].size();
}
return a;
}
private void writeData(ByteBuffer bb)
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < transparentQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, transparentQuads[dir].get(i));
}
if (i >= transparentQuads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && transparentQuads[dir].isEmpty())
{
dir++;
}
}
else
{
quad = i;
}
}
};
}
//=========//
// getters //
//=========//
@@ -193,17 +193,7 @@ public class GLProxy
return instance;
}
public EDhApiGpuUploadMethod getGpuUploadMethod()
{
EDhApiGpuUploadMethod method = Config.Client.Advanced.GpuBuffers.gpuUploadMethod.get();
if (!this.bufferStorageSupported && method == EDhApiGpuUploadMethod.BUFFER_STORAGE)
{
// if buffer storage isn't supported
// default to DATA since that is the most compatible
method = EDhApiGpuUploadMethod.DATA;
}
return method == EDhApiGpuUploadMethod.AUTO ? this.preferredUploadMethod : method;
}
public EDhApiGpuUploadMethod getGpuUploadMethod() { return this.preferredUploadMethod; }
public boolean runningOnRenderThread()
{
@@ -238,7 +238,7 @@ public class GLBuffer implements AutoCloseable
// buffer mapping //
//================//
public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpensionSize, int bufferHint, int mapFlags)
public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpansionSize, int bufferHint, int mapFlags)
{
LodUtil.assertTrue(targetSize != 0, "MapBuffer targetSize is 0");
LodUtil.assertTrue(uploadMethod.useEarlyMapping, "Upload method must be one that use early mappings in order to call mapBuffer");
@@ -252,7 +252,7 @@ public class GLBuffer implements AutoCloseable
if (this.size < targetSize || this.size > targetSize * BUFFER_SHRINK_TRIGGER)
{
int newSize = (int) (targetSize * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxExpensionSize) newSize = maxExpensionSize;
if (newSize > maxExpansionSize) newSize = maxExpansionSize;
this.size = newSize;
if (this.bufferStorage)
{
@@ -77,9 +77,9 @@ public class GLVertexBuffer extends GLBuffer
this.vertexCount = vertCount;
}
public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpensionSize)
public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpansionSize)
{
return super.mapBuffer(targetSize, uploadMethod, maxExpensionSize,
return super.mapBuffer(targetSize, uploadMethod, maxExpansionSize,
uploadMethod.useBufferStorage ? GL32.GL_MAP_WRITE_BIT :
uploadMethod.useEarlyMapping ? GL32.GL_DYNAMIC_DRAW : GL32.GL_STATIC_DRAW,
GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT);