Add and improve buffer upload methods

This commit is contained in:
tom lee
2022-02-20 00:42:27 +08:00
parent c450f5b960
commit a1652fe68a
5 changed files with 122 additions and 59 deletions
@@ -20,7 +20,7 @@
package com.seibel.lod.core.enums.config;
/**
* Auto, Buffer_Storage, Sub_Data, Buffer_Mapping, Data
* Auto, BUFFER_STORAGE_MAPPING, Buffer_Storage, Sub_Data, Buffer_Mapping, Data
*
* @author James Seibel
* @version 12-1-2021
@@ -28,19 +28,23 @@ package com.seibel.lod.core.enums.config;
public enum GpuUploadMethod
{
/** Picks the best option based on the GPU the user has. */
AUTO,
AUTO(false, false),
/*
*/
BUFFER_STORAGE_MAPPING(true, true),
/**
* Default for NVIDIA if OpenGL 4.5 is supported. <br>
* Fast rendering, no stuttering.
*/
BUFFER_STORAGE,
BUFFER_STORAGE(false, true),
/**
* Backup option for NVIDIA. <br>
* Fast rendering but may stutter when uploading.
*/
SUB_DATA,
SUB_DATA(false, false),
/**
* Default option for AMD/Intel. <br>
@@ -48,12 +52,19 @@ public enum GpuUploadMethod
* Fast rending if in GPU memory, slow if in system memory, <br>
* but won't stutter when uploading.
*/
BUFFER_MAPPING,
BUFFER_MAPPING(true, false),
/**
* Backup option for AMD/Intel. <br>
* Fast rendering but may stutter when uploading.
*/
DATA,
DATA(false, false);
public final boolean useEarlyMapping;
public final boolean useBufferStorage;
GpuUploadMethod(boolean useEarlyMapping, boolean useBufferStorage) {
this.useEarlyMapping = useEarlyMapping;
this.useBufferStorage = useBufferStorage;
}
}
@@ -28,7 +28,7 @@ public abstract class RenderRegion implements AutoCloseable
}
return null;
}
public abstract void uploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod);
public abstract boolean shouldRender(IMinecraftRenderWrapper renderer, boolean enableDirectionalCulling);
public abstract void render(LodRenderProgram shaderProgram);
@@ -4,9 +4,11 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.function.Function;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.LodDirection.Axis;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.util.ColorUtil;
public class LodQuadBuilder {
@@ -177,6 +179,33 @@ public class LodQuadBuilder {
}
};
}
public interface BufferFiller {
boolean fill(LodVertexBuffer vbo); // If true: means more data is needed to be filled
}
public BufferFiller makeBufferFiller(GpuUploadMethod method) {
int numOfBuffers = getCurrentNeededVertexBuffers();
return new BufferFiller() {
int counter = 0;
public boolean fill(LodVertexBuffer vbo) {
if (counter >= numOfBuffers) {
return false;
}
int numOfQuads = MAX_QUADS_PER_BUFFER;
if (quads.size()-(counter*MAX_QUADS_PER_BUFFER) < MAX_QUADS_PER_BUFFER)
numOfQuads = quads.size()-(counter*MAX_QUADS_PER_BUFFER);
if (numOfQuads != 0) {
ByteBuffer bb = vbo.mapBuffer(numOfQuads*QUAD_BYTE_SIZE, method, MAX_QUADS_PER_BUFFER * QUAD_BYTE_SIZE);
if (bb == null) throw new NullPointerException("mapBuffer returned null");
writeVertexData(bb, MAX_QUADS_PER_BUFFER * counter++, numOfQuads).rewind();
vbo.unmapBuffer(method);
}
vbo.vertexCount = numOfQuads*6;
return counter < numOfBuffers;
}
};
}
public int getCurrentNeededVertexBuffers() {
return quads.size() / MAX_QUADS_PER_BUFFER + 1;
@@ -70,55 +70,17 @@ public class LodVertexBuffer implements AutoCloseable
GLProxy.getInstance().recordOpenGlCall(() -> GL32.glDeleteBuffers(id));
}
this.id = -1;
size = 0;
vertexCount = 0;
count--;
}
private void _uploadBufferStorage(ByteBuffer bb, int maxExpensionSize) {
if (!isBufferStorage) throw new IllegalStateException("Buffer isn't bufferStorage but its trying to use BufferStorage upload method!");
int bbSize = bb.limit() - bb.position();
if (size < bbSize || size > bbSize * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) {
int newSize = (int) (bbSize * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxExpensionSize) newSize = maxExpensionSize;
GL32.glDeleteBuffers(id);
id = GL32.glGenBuffers();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id);
GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, newSize, GL44.GL_MAP_WRITE_BIT);
size = newSize;
} else {
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id);
}
// map buffer range is better since it can be explicitly unsynchronized
ByteBuffer vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, bbSize,
GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT);
if (vboBuffer == null) {
ClientApi.LOGGER.error("MapBufferRange Failed: bbSize: {}, maxSize: {}, size: {}", bbSize, maxExpensionSize, size);
}
vboBuffer.put(bb);
GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER);
}
// 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.
private void _uploadBufferMapping(ByteBuffer bb, int maxExpensionSize) {
if (isBufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use BufferMapping upload method!");
int bbSize = bb.limit() - bb.position();
private void _uploadBufferStorage(ByteBuffer bb) {
if (!isBufferStorage) throw new IllegalStateException("Buffer is not bufferStorage but its trying to use bufferStorage upload method!");
GL32.glDeleteBuffers(id);
id = GL32.glGenBuffers();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id);
if (size < bbSize || size > bbSize * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) {
int newSize = (int) (bbSize * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxExpensionSize) newSize = maxExpensionSize;
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
size = newSize;
}
// map buffer range is better since it can be explicitly unsynchronized
ByteBuffer vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, bbSize,
GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT);
vboBuffer.put(bb);
GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER);
GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, bb, 0);
}
// bufferData
@@ -148,13 +110,15 @@ public class LodVertexBuffer implements AutoCloseable
public void uploadBuffer(ByteBuffer bb, int vertCount, GpuUploadMethod uploadMethod, int maxExpensionSize) {
if (vertCount < 0) throw new IllegalArgumentException("VertCount is negative!");
if (uploadMethod.useEarlyMapping)
throw new IllegalArgumentException("UploadMethod signal that this should use Mapping instead of uploadBuffer!");
vertexCount = vertCount;
int bbSize = bb.limit()-bb.position();
if (bbSize > maxExpensionSize)
throw new IllegalArgumentException("maxExpensionSize is "+maxExpensionSize+" but buffer size is "+bbSize+"!");
// If size is zero, just ignore it.
if (bbSize == 0) return;
boolean useBuffStorage = uploadMethod == GpuUploadMethod.BUFFER_STORAGE;
boolean useBuffStorage = uploadMethod.useBufferStorage;
if (useBuffStorage != isBufferStorage) {
_destroy();
_create(useBuffStorage);
@@ -163,11 +127,8 @@ public class LodVertexBuffer implements AutoCloseable
switch (uploadMethod) {
case AUTO:
throw new IllegalArgumentException("GpuUploadMethod AUTO must be resolved before call to uploadBuffer()!");
case BUFFER_MAPPING:
_uploadBufferMapping(bb, maxExpensionSize);
break;
case BUFFER_STORAGE:
_uploadBufferStorage(bb, maxExpensionSize);
_uploadBufferStorage(bb);
break;
case DATA:
_uploadData(bb);
@@ -198,4 +159,49 @@ public class LodVertexBuffer implements AutoCloseable
}
}
private boolean isMapped = false;
public ByteBuffer mapBuffer(int targetSize, GpuUploadMethod uploadMethod, int maxExpensionSize)
{
if (targetSize == 0) throw new IllegalArgumentException("MapBuffer targetSize is 0!");
if (!uploadMethod.useEarlyMapping) throw new IllegalStateException("Upload method must be one that use mappings in order to call mapBuffer!");
if (isMapped) throw new IllegalStateException("Map Buffer called but buffer is already mapped!");
boolean useBuffStorage = uploadMethod.useBufferStorage;
if (useBuffStorage != isBufferStorage) {
_destroy();
_create(useBuffStorage);
}
ByteBuffer vboBuffer;
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id);
if (size < targetSize || size > targetSize * BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER) {
int newSize = (int) (targetSize * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxExpensionSize) newSize = maxExpensionSize;
size = newSize;
if (uploadMethod.useBufferStorage) {
GL32.glDeleteBuffers(id);
id = GL32.glGenBuffers();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id);
GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, newSize, GL44.GL_MAP_WRITE_BIT);
} else {
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
}
}
vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, targetSize,
GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT);
isMapped = true;
return vboBuffer;
}
public void unmapBuffer(GpuUploadMethod uploadMethod)
{
if (!uploadMethod.useEarlyMapping) throw new IllegalStateException("Upload method must be one that use mappings in order to call unmapBuffer!");
if (!isMapped) throw new IllegalStateException("Unmap Buffer called but buffer is already not mapped!");
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id);
GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER);
isMapped = false;
}
}
@@ -10,6 +10,7 @@ import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.objects.RenderRegion;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.opengl.LodQuadBuilder.BufferFiller;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.render.RenderUtil;
import com.seibel.lod.core.util.LodUtil;
@@ -68,19 +69,35 @@ public class SimpleRenderRegion extends RenderRegion {
}
return vbos[iIndex];
}
private void uploadBuffersViaMapping(LodQuadBuilder builder, GpuUploadMethod uploadMethod)
{
resize(builder.getCurrentNeededVertexBuffers());
for (int i=0; i<vbos.length; i++) {
if (vbos[i]==null) vbos[i] = new LodVertexBuffer(uploadMethod.useBufferStorage);
}
BufferFiller func = builder.makeBufferFiller(uploadMethod);
int i = 0;
while (i < vbos.length && func.fill(vbos[i++])) {}
}
@Override
public void uploadBuffers(LodQuadBuilder builder, GpuUploadMethod uploadMethod)
{
if (uploadMethod.useEarlyMapping) {
uploadBuffersViaMapping(builder, uploadMethod);
return;
}
resize(builder.getCurrentNeededVertexBuffers());
long remainingNS = 0;
long BPerNS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds();
int i = 0;
Iterator<ByteBuffer> iter = builder.makeVertexBuffers();
while (iter.hasNext()) {
ByteBuffer bb = iter.next();
LodVertexBuffer vbo = getOrMakeVbo(i++, uploadMethod==GpuUploadMethod.BUFFER_STORAGE);
LodVertexBuffer vbo = getOrMakeVbo(i++, uploadMethod.useBufferStorage);
int size = bb.limit() - bb.position();
vbo.uploadBuffer(bb, size/LodUtil.LOD_VERTEX_FORMAT.getByteSize(), uploadMethod, FULL_SIZED_BUFFERS);
// upload buffers over an extended period of time