refactor ColumnRenderBuffer

This commit is contained in:
James Seibel
2023-03-06 20:30:59 -06:00
parent b63c4b9e7e
commit a517e6997b
4 changed files with 484 additions and 316 deletions
@@ -64,9 +64,13 @@ public enum EGpuUploadMethod
*/
DATA(false, false);
public final boolean useEarlyMapping;
public final boolean useBufferStorage;
EGpuUploadMethod(boolean useEarlyMapping, boolean useBufferStorage) {
EGpuUploadMethod(boolean useEarlyMapping, boolean useBufferStorage)
{
this.useEarlyMapping = useEarlyMapping;
this.useBufferStorage = useBufferStorage;
}
@@ -349,7 +349,7 @@ public class ColumnRenderSource
//LOGGER.info("attempting to build buffer for: "+renderSection.pos);
}
}
this.buildRenderBufferFuture = ColumnRenderBuffer.build(level, this.columnRenderBufferRef, this, columnRenderSources);
this.buildRenderBufferFuture = ColumnRenderBuffer.buildBuffers(level, this.columnRenderBufferRef, this, columnRenderSources);
}
}
@@ -34,10 +34,10 @@ import static com.seibel.lod.core.render.glObject.GLProxy.GL_LOGGER;
public class ColumnRenderBuffer extends AbstractRenderBuffer
{
//TODO: Make the pool change thread count after the config value is changed
public static final ExecutorService BUFFER_BUILDERS = ThreadUtil.makeThreadPool(Config.Client.Advanced.Threading.numberOfBufferBuilderThreads.get(), "BufferBuilder");
public static final ExecutorService BUFFER_BUILDER_THREADS = ThreadUtil.makeThreadPool(Config.Client.Advanced.Threading.numberOfBufferBuilderThreads.get(), "BufferBuilder");
public static final ExecutorService BUFFER_UPLOADER = ThreadUtil.makeSingleThreadPool("ColumnBufferUploader");
public static final int MAX_CONCURRENT_CALL = 8;
public static final ConfigBasedLogger EVENT_LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
() -> Config.Client.Advanced.Debugging.DebugSwitch.logRendererBufferEvent.get());
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
@@ -46,183 +46,25 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
GLVertexBuffer[] vbosTransparent;
public final DhBlockPos pos;
private boolean closed = false;
public ColumnRenderBuffer(DhBlockPos pos) {
this.pos = pos;
vbos = new GLVertexBuffer[0];
vbosTransparent = new GLVertexBuffer[0];
}
public ColumnRenderBuffer(DhBlockPos pos)
{
this.pos = pos;
vbos = new GLVertexBuffer[0];
vbosTransparent = new GLVertexBuffer[0];
}
private static void _doUploadBuffersDirect(GLVertexBuffer[] vbos, Iterator<ByteBuffer> iter, EGpuUploadMethod method) throws InterruptedException {
long remainingNS = 0;
long BPerNS = Config.Client.Advanced.Buffers.gpuUploadPerMegabyteInMilliseconds.get();
int i = 0;
while (iter.hasNext()) {
if (i >= vbos.length) {
throw new RuntimeException("Too many vertex buffers!!");
}
ByteBuffer bb = iter.next();
GLVertexBuffer vbo = getOrMake(vbos, i++, method.useBufferStorage);
int size = bb.limit() - bb.position();
try {
vbo.bind();
vbo.uploadBuffer(bb, size/LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
} catch (Exception e) {
vbos[i-1] = null;
vbo.close();
LOGGER.error("Failed to upload buffer: ", e);
}
if (BPerNS<=0) continue;
// upload buffers over an extended period of time
// to hopefully prevent stuttering.
remainingNS += size * 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;
}
}
if (i < vbos.length) {
throw new RuntimeException("Too few vertex buffers!!");
}
}
private void _uploadBuffersDirect(LodQuadBuilder builder, EGpuUploadMethod method) throws InterruptedException {
vbos = resize(vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
_doUploadBuffersDirect(vbos, builder.makeOpaqueVertexBuffers(), method);
vbosTransparent = resize(vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
_doUploadBuffersDirect(vbosTransparent, builder.makeTransparentVertexBuffers(), method);
}
private void _uploadBuffersMapped(LodQuadBuilder builder, EGpuUploadMethod method)
{
vbos = resize(vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
for (int i=0; i<vbos.length; i++) {
if (vbos[i]==null) vbos[i] = new GLVertexBuffer(method.useBufferStorage);
}
LodQuadBuilder.BufferFiller func = builder.makeOpaqueBufferFiller(method);
{
int i = 0;
while (i < vbos.length && func.fill(vbos[i++])) {
}
}
vbosTransparent = resize(vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
for (int i=0; i<vbosTransparent.length; i++) {
if (vbosTransparent[i]==null) vbosTransparent[i] = new GLVertexBuffer(method.useBufferStorage);
}
func = builder.makeTransparentBufferFiller(method);
{
int i = 0;
while (i < vbosTransparent.length && func.fill(vbosTransparent[i++])) {
}
}
}
private static GLVertexBuffer[] resize(GLVertexBuffer[] vbos, int newSize) {
if (vbos.length == newSize) return vbos;
GLVertexBuffer[] newVbos = new GLVertexBuffer[newSize];
System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize));
if (newSize < vbos.length) {
for (int i = newSize; i < vbos.length; i++) {
if (vbos[i] != null) {
vbos[i].close();
}
}
}
return newVbos;
}
private static GLVertexBuffer getOrMake(GLVertexBuffer[] vbos, int iIndex, boolean useBuffStorage) {
if (vbos[iIndex] == null) {
vbos[iIndex] = new GLVertexBuffer(useBuffStorage);
}
return vbos[iIndex];
}
public void uploadBuffer(LodQuadBuilder builder, EGpuUploadMethod method) throws InterruptedException {
if (method.useEarlyMapping) {
_uploadBuffersMapped(builder, method);
} else {
_uploadBuffersDirect(builder, method);
}
}
@Override
public boolean renderOpaque(LodRenderer renderContext) {
boolean hasRendered = false;
renderContext.setupOffset(pos);
for (GLVertexBuffer vbo : vbos) {
if (vbo == null) continue;
if (vbo.getVertexCount() == 0) continue;
hasRendered = true;
renderContext.drawVbo(vbo);
//LodRenderer.tickLogger.info("Vertex buffer: {}", vbo);
}
return hasRendered;
}
@Override
public boolean renderTransparent(LodRenderer renderContext) {
boolean hasRendered = false;
renderContext.setupOffset(pos);
for (GLVertexBuffer vbo : vbosTransparent) {
if (vbo == null) continue;
if (vbo.getVertexCount() == 0) continue;
hasRendered = true;
renderContext.drawVbo(vbo);
//LodRenderer.tickLogger.info("Vertex buffer: {}", vbo);
}
return hasRendered;
}
@Override
public void debugDumpStats(StatsMap statsMap) {
statsMap.incStat("RenderBuffers");
statsMap.incStat("SimpleRenderBuffers");
for (GLVertexBuffer b : vbos) {
if (b == null) continue;
statsMap.incStat("VBOs");
if (b.getSize() == FULL_SIZED_BUFFER) {
statsMap.incStat("FullsizedVBOs");
}
if (b.getSize() == 0) GL_LOGGER.warn("VBO with size 0");
statsMap.incBytesStat("TotalUsage", b.getSize());
}
}
private boolean closed = false;
@Override
public void close() {
if (closed) return;
closed = true;
GLProxy.getInstance().recordOpenGlCall(() -> {
for (GLVertexBuffer b : vbos) {
if (b == null) continue;
b.destroy(false);
}
for (GLVertexBuffer b : vbosTransparent) {
if (b == null) continue;
b.destroy(false);
}
});
}
private static long getCurrentJobsCount() {
long jobs = ((ThreadPoolExecutor) BUFFER_BUILDERS).getQueue().stream().filter(t -> !((Future<?>) t).isDone()).count();
jobs += ((ThreadPoolExecutor) BUFFER_UPLOADER).getQueue().stream().filter(t -> !((Future<?>) t).isDone()).count();
return jobs;
}
public static boolean isBusy() {
return getCurrentJobsCount() > MAX_CONCURRENT_CALL;
}
public static CompletableFuture<ColumnRenderBuffer> build(IDhClientLevel clientLevel, Reference<ColumnRenderBuffer> usedBufferSlot, ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
//==============//
// vbo building //
//==============//
/** @return null if busy */
public static CompletableFuture<ColumnRenderBuffer> buildBuffers(IDhClientLevel clientLevel, Reference<ColumnRenderBuffer> usedBufferSlot, ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
{
if (isBusy())
{
@@ -230,13 +72,13 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
}
//LOGGER.info("RenderRegion startBuild @ {}", renderSource.sectionPos);
return CompletableFuture.supplyAsync(() ->
return CompletableFuture.supplyAsync(() ->
{
try
{
boolean enableTransparency = Config.Client.Graphics.Quality.transparency.get().tranparencyEnabled;
EVENT_LOGGER.trace("RenderRegion start QuadBuild @ {}", renderSource.sectionPos);
EVENT_LOGGER.trace("RenderRegion start QuadBuild @ "+renderSource.sectionPos);
boolean enableSkyLightCulling = Config.Client.Graphics.AdvancedGraphics.enableCaveCulling.get();
int skyLightCullingBelow = Config.Client.Graphics.AdvancedGraphics.caveCullingHeight.get();
@@ -247,7 +89,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
(short) (skyLightCullingBelow - clientLevel.getMinY()), enableTransparency);
makeLodRenderData(builder, renderSource, adjData);
EVENT_LOGGER.trace("RenderRegion end QuadBuild @ {}", renderSource.sectionPos);
EVENT_LOGGER.trace("RenderRegion end QuadBuild @ "+renderSource.sectionPos);
return builder;
}
catch (UncheckedInterruptedException e)
@@ -260,48 +102,51 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
throw e3;
}
}, BUFFER_BUILDERS)
.thenApplyAsync((quadBuilder) ->
{
try
}, BUFFER_BUILDER_THREADS)
.thenApplyAsync((quadBuilder) ->
{
EVENT_LOGGER.trace("RenderRegion start Upload @ {}", renderSource.sectionPos);
GLProxy glProxy = GLProxy.getInstance();
EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
EGLProxyContext oldContext = glProxy.getGlContext();
glProxy.setGlContext(EGLProxyContext.LOD_BUILDER);
ColumnRenderBuffer buffer = usedBufferSlot.swap(null);
if (buffer == null)
buffer = new ColumnRenderBuffer(
new DhBlockPos(renderSource.sectionPos.getCorner().getCornerBlockPos(), clientLevel.getMinY())
);
try
{
buffer.uploadBuffer(quadBuilder, method);
EVENT_LOGGER.trace("RenderRegion end Upload @ {}", renderSource.sectionPos);
return buffer;
EVENT_LOGGER.trace("RenderRegion start Upload @ {}", renderSource.sectionPos);
GLProxy glProxy = GLProxy.getInstance();
EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
EGLProxyContext oldContext = glProxy.getGlContext();
glProxy.setGlContext(EGLProxyContext.LOD_BUILDER);
ColumnRenderBuffer buffer = usedBufferSlot.swap(null);
if (buffer == null)
{
buffer = new ColumnRenderBuffer(new DhBlockPos(renderSource.sectionPos.getCorner().getCornerBlockPos(), clientLevel.getMinY()));
}
try
{
buffer.uploadBuffer(quadBuilder, method);
EVENT_LOGGER.trace("RenderRegion end Upload @ {}", renderSource.sectionPos);
return buffer;
}
catch (Exception e)
{
buffer.close();
throw e;
}
finally
{
glProxy.setGlContext(oldContext);
}
}
catch (Exception e)
catch (InterruptedException e)
{
buffer.close();
throw e;
throw UncheckedInterruptedException.convert(e);
}
finally
catch (Throwable e3)
{
glProxy.setGlContext(oldContext);
LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
throw e3;
}
}
catch (InterruptedException e)
},
BUFFER_UPLOADER).handle((v, e) ->
{
throw UncheckedInterruptedException.convert(e);
}
catch (Throwable e3)
{
LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
throw e3;
}
}, BUFFER_UPLOADER).handle((v, e) -> {
//LOGGER.info("RenderRegion endBuild @ {}", renderSource.sectionPos);
if (e != null)
{
@@ -311,6 +156,7 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
buffer = usedBufferSlot.swap(null);
buffer.close();
}
return null;
}
else
@@ -319,107 +165,425 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer
}
});
}
private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource region, ColumnRenderSource[] adjRegions) {
// Variable initialization
EDebugMode debugMode = Config.Client.Advanced.Debugging.debugMode.get();
byte detailLevel = region.getDataDetail();
for (int x = 0; x < ColumnRenderSource.SECTION_SIZE; x++) {
for (int z = 0; z < ColumnRenderSource.SECTION_SIZE; z++) {
UncheckedInterruptedException.throwIfInterrupted();
ColumnArrayView posData = region.getVerticalDataPointView(x, z);
if (posData.size() == 0 || !RenderDataPointUtil.doesDataPointExist(posData.get(0))
|| RenderDataPointUtil.isVoid(posData.get(0)))
continue;
ColumnRenderSource.DebugSourceFlag debugSourceFlag = region.debugGetFlag(x, z);
ColumnArrayView[][] adjData = new ColumnArrayView[4][];
// 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
// 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
for (ELodDirection lodDirection : ELodDirection.ADJ_DIRECTIONS) {
try {
int xAdj = x + lodDirection.getNormal().x;
int zAdj = z + lodDirection.getNormal().z;
boolean isCrossRegionBoundary = (xAdj < 0 || xAdj >= ColumnRenderSource.SECTION_SIZE) ||
(zAdj < 0 || zAdj >= ColumnRenderSource.SECTION_SIZE);
ColumnRenderSource adjRegion;
byte adjDetail;
//we check if the detail of the adjPos is equal to the correct one (region border fix)
//or if the detail is wrong by 1 value (region+circle border fix)
if (isCrossRegionBoundary) {
//we compute at which detail that position should be rendered
adjRegion = adjRegions[lodDirection.ordinal()-2];
if(adjRegion == null) continue;
adjDetail = adjRegion.getDataDetail();
if (adjDetail != detailLevel) {
//TODO: Implement this
} else {
if (xAdj < 0) xAdj += ColumnRenderSource.SECTION_SIZE;
if (zAdj < 0) zAdj += ColumnRenderSource.SECTION_SIZE;
if (xAdj >= ColumnRenderSource.SECTION_SIZE) xAdj -= ColumnRenderSource.SECTION_SIZE;
if (zAdj >= ColumnRenderSource.SECTION_SIZE) zAdj -= ColumnRenderSource.SECTION_SIZE;
}
} else {
adjRegion = region;
adjDetail = detailLevel;
}
if (adjDetail < detailLevel-1 || adjDetail > detailLevel+1) {
continue;
}
if (adjDetail == detailLevel || adjDetail > detailLevel) {
adjData[lodDirection.ordinal() - 2] = new ColumnArrayView[1];
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getVerticalDataPointView(xAdj, zAdj);
} else {
adjData[lodDirection.ordinal() - 2] = new ColumnArrayView[2];
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getVerticalDataPointView(xAdj, zAdj);
adjData[lodDirection.ordinal() - 2][1] = adjRegion.getVerticalDataPointView(
xAdj + (lodDirection.getAxis()== ELodDirection.Axis.X ? 0 : 1),
zAdj + (lodDirection.getAxis()== ELodDirection.Axis.Z ? 0 : 1));
}
} catch (RuntimeException e) {
EVENT_LOGGER.warn("Failed to get adj data for [{}:{},{}] at [{}]", detailLevel, x, z, lodDirection);
EVENT_LOGGER.warn("Detail exception: ", e);
}
}
// We render every vertical lod present in this position
// We only stop when we find a block that is void or non-existing block
for (int i = 0; i < posData.size(); i++) {
long data = posData.get(i);
// If the data is not renderable (Void or non-existing) we stop since there is
// no data left in this position
if (RenderDataPointUtil.isVoid(data) || !RenderDataPointUtil.doesDataPointExist(data))
break;
long adjDataTop = i - 1 >= 0 ? posData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA;
long adjDataBot = i + 1 < posData.size() ? posData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA;
CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, detailLevel,
x, z, quadBuilder, debugMode, debugSourceFlag);
}
}
}
quadBuilder.mergeQuads();
private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource region, ColumnRenderSource[] adjRegions)
{
// Variable initialization
EDebugMode debugMode = Config.Client.Advanced.Debugging.debugMode.get();
byte detailLevel = region.getDataDetail();
for (int x = 0; x < ColumnRenderSource.SECTION_SIZE; x++)
{
for (int z = 0; z < ColumnRenderSource.SECTION_SIZE; z++)
{
UncheckedInterruptedException.throwIfInterrupted();
ColumnArrayView posData = region.getVerticalDataPointView(x, z);
if (posData.size() == 0
|| !RenderDataPointUtil.doesDataPointExist(posData.get(0))
|| RenderDataPointUtil.isVoid(posData.get(0)))
{
continue;
}
ColumnRenderSource.DebugSourceFlag debugSourceFlag = region.debugGetFlag(x, z);
ColumnArrayView[][] adjData = new ColumnArrayView[4][];
// 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
// 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
for (ELodDirection lodDirection : ELodDirection.ADJ_DIRECTIONS)
{
try
{
int xAdj = x + lodDirection.getNormal().x;
int zAdj = z + lodDirection.getNormal().z;
boolean isCrossRegionBoundary =
(xAdj < 0 || xAdj >= ColumnRenderSource.SECTION_SIZE) ||
(zAdj < 0 || zAdj >= ColumnRenderSource.SECTION_SIZE);
ColumnRenderSource adjRegion;
byte adjDetail;
//we check if the detail of the adjPos is equal to the correct one (region border fix)
//or if the detail is wrong by 1 value (region+circle border fix)
if (isCrossRegionBoundary)
{
//we compute at which detail that position should be rendered
adjRegion = adjRegions[lodDirection.ordinal() - 2];
if (adjRegion == null)
{
continue;
}
adjDetail = adjRegion.getDataDetail();
if (adjDetail != detailLevel)
{
//TODO: Implement this
}
else
{
if (xAdj < 0)
xAdj += ColumnRenderSource.SECTION_SIZE;
if (zAdj < 0)
zAdj += ColumnRenderSource.SECTION_SIZE;
if (xAdj >= ColumnRenderSource.SECTION_SIZE)
xAdj -= ColumnRenderSource.SECTION_SIZE;
if (zAdj >= ColumnRenderSource.SECTION_SIZE)
zAdj -= ColumnRenderSource.SECTION_SIZE;
}
}
else
{
adjRegion = region;
adjDetail = detailLevel;
}
if (adjDetail < detailLevel - 1 || adjDetail > detailLevel + 1)
{
continue;
}
if (adjDetail == detailLevel || adjDetail > detailLevel)
{
adjData[lodDirection.ordinal() - 2] = new ColumnArrayView[1];
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getVerticalDataPointView(xAdj, zAdj);
}
else
{
adjData[lodDirection.ordinal() - 2] = new ColumnArrayView[2];
adjData[lodDirection.ordinal() - 2][0] = adjRegion.getVerticalDataPointView(xAdj, zAdj);
adjData[lodDirection.ordinal() - 2][1] = adjRegion.getVerticalDataPointView(
xAdj + (lodDirection.getAxis() == ELodDirection.Axis.X ? 0 : 1),
zAdj + (lodDirection.getAxis() == ELodDirection.Axis.Z ? 0 : 1));
}
}
catch (RuntimeException e)
{
EVENT_LOGGER.warn("Failed to get adj data for [{}:{},{}] at [{}]", detailLevel, x, z, lodDirection);
EVENT_LOGGER.warn("Detail exception: ", e);
}
} // for adjacent directions
// We render every vertical lod present in this position
// We only stop when we find a block that is void or non-existing block
for (int i = 0; i < posData.size(); i++)
{
long data = posData.get(i);
// If the data is not renderable (Void or non-existing) we stop since there is
// no data left in this position
if (RenderDataPointUtil.isVoid(data) || !RenderDataPointUtil.doesDataPointExist(data))
{
break;
}
long adjDataTop = i - 1 >= 0 ? posData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA;
long adjDataBot = i + 1 < posData.size() ? posData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA;
CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, detailLevel,
x, z, quadBuilder, debugMode, debugSourceFlag);
}
}// for z
}// for x
quadBuilder.mergeQuads();
}
//==================//
// buffer uploading //
//==================//
public void uploadBuffer(LodQuadBuilder builder, EGpuUploadMethod method) throws InterruptedException
{
if (method.useEarlyMapping)
{
this.uploadBuffersMapped(builder, method);
}
else
{
this.uploadBuffersDirect(builder, method);
}
}
private void uploadBuffersMapped(LodQuadBuilder builder, EGpuUploadMethod method)
{
// opaque vbos //
this.vbos = resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
for (int i = 0; i < this.vbos.length; i++)
{
if (this.vbos[i] == null)
{
this.vbos[i] = new GLVertexBuffer(method.useBufferStorage);
}
}
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);
}
}
private void uploadBuffersDirect(LodQuadBuilder builder, EGpuUploadMethod 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, EGpuUploadMethod method) throws InterruptedException
{
long remainingNS = 0;
long BPerNS = Config.Client.Advanced.Buffers.gpuUploadPerMegabyteInMilliseconds.get();
int vboIndex = 0;
while (iter.hasNext())
{
if (vboIndex >= vbos.length)
{
throw new RuntimeException("Too many vertex buffers!!");
}
ByteBuffer bb = iter.next();
GLVertexBuffer vbo = getOrMake(vbos, vboIndex++, method.useBufferStorage);
int size = bb.limit() - bb.position();
try
{
vbo.bind();
vbo.uploadBuffer(bb, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
}
catch (Exception e)
{
vbos[vboIndex - 1] = null;
vbo.close();
LOGGER.error("Failed to upload buffer: ", e);
}
if (BPerNS <= 0)
{
continue;
}
// upload buffers over an extended period of time
// to hopefully prevent stuttering.
remainingNS += size * 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;
}
}
if (vboIndex < vbos.length)
{
throw new RuntimeException("Too few vertex buffers!!");
}
}
//=================//
// vbo interaction //
//=================//
private static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
{
if (vbos.length == newSize)
{
return vbos;
}
GLVertexBuffer[] newVbos = new GLVertexBuffer[newSize];
System.arraycopy(vbos, 0, newVbos, 0, Math.min(vbos.length, newSize));
if (newSize < vbos.length)
{
for (int i = newSize; i < vbos.length; i++)
{
if (vbos[i] != null)
{
vbos[i].close();
}
}
}
return newVbos;
}
private static GLVertexBuffer getOrMake(GLVertexBuffer[] vbos, int iIndex, boolean useBuffStorage)
{
if (vbos[iIndex] == null)
{
vbos[iIndex] = new GLVertexBuffer(useBuffStorage);
}
return vbos[iIndex];
}
//========//
// render //
//========//
@Override
public boolean renderOpaque(LodRenderer renderContext)
{
boolean hasRendered = false;
renderContext.setupOffset(this.pos);
for (GLVertexBuffer vbo : this.vbos)
{
if (vbo == null)
{
continue;
}
if (vbo.getVertexCount() == 0)
{
continue;
}
hasRendered = true;
renderContext.drawVbo(vbo);
//LodRenderer.tickLogger.info("Vertex buffer: {}", vbo);
}
return hasRendered;
}
@Override
public boolean renderTransparent(LodRenderer renderContext)
{
boolean hasRendered = false;
renderContext.setupOffset(this.pos);
for (GLVertexBuffer vbo : this.vbosTransparent)
{
if (vbo == null)
{
continue;
}
if (vbo.getVertexCount() == 0)
{
continue;
}
hasRendered = true;
renderContext.drawVbo(vbo);
//LodRenderer.tickLogger.info("Vertex buffer: {}", vbo);
}
return hasRendered;
}
//==============//
// misc methods //
//==============//
@Override
public void debugDumpStats(StatsMap statsMap)
{
statsMap.incStat("RenderBuffers");
statsMap.incStat("SimpleRenderBuffers");
for (GLVertexBuffer vertexBuffer : vbos)
{
if (vertexBuffer != null)
{
statsMap.incStat("VBOs");
if (vertexBuffer.getSize() == FULL_SIZED_BUFFER)
{
statsMap.incStat("FullsizedVBOs");
}
if (vertexBuffer.getSize() == 0)
{
GL_LOGGER.warn("VBO with size 0");
}
statsMap.incBytesStat("TotalUsage", vertexBuffer.getSize());
}
}
}
@Override
public void close()
{
if (this.closed)
{
return;
}
this.closed = true;
GLProxy.getInstance().recordOpenGlCall(() ->
{
for (GLVertexBuffer buffer : this.vbos)
{
if (buffer != null)
{
buffer.destroy(false);
}
}
for (GLVertexBuffer buffer : this.vbosTransparent)
{
if (buffer != null)
{
buffer.destroy(false);
}
}
});
}
//=========//
// getters //
//=========//
private static long getCurrentJobsCount()
{
long jobs = ((ThreadPoolExecutor) BUFFER_BUILDER_THREADS).getQueue().stream().filter(runnable -> !((Future<?>) runnable).isDone()).count();
jobs += ((ThreadPoolExecutor) BUFFER_UPLOADER).getQueue().stream().filter(runnable -> !((Future<?>) runnable).isDone()).count();
return jobs;
}
public static boolean isBusy() { return getCurrentJobsCount() > MAX_CONCURRENT_CALL; }
}
@@ -30,9 +30,9 @@ public abstract class AbstractRenderBuffer implements AutoCloseable
// ======================================================================
// ========== Called by render thread ==========
/* Called on... well... rendering.
* Return false if nothing rendered. (Optional) */
/** @return true if something was rendered, false otherwise */
public abstract boolean renderOpaque(LodRenderer renderContext);
/** @return true if something was rendered, false otherwise */
public abstract boolean renderTransparent(LodRenderer renderContext);
// ========== Called by any thread. (thread safe) ==========