Add and improve buffer upload methods
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user