This commit is contained in:
James Seibel
2022-04-14 19:37:35 -05:00
18 changed files with 705 additions and 409 deletions
@@ -92,8 +92,11 @@ public class LodBufferBuilderFactory {
* when need be to fit the larger sizes.
*/
public static final int DEFAULT_MEMORY_ALLOCATION = (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3) * 8;
public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * (LodRenderer.ENABLE_IBO ? 4 : 6);
public static final int MAX_TRIANGLES_PER_BUFFER = (1024 * 1024 * 1)
/ (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3);
public static final int MAX_QUADS_PER_BUFFER = (1024 * 1024 * 1) / QUADS_BYTE_SIZE;
public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
public static int skyLightPlayer = 15;
@@ -30,7 +30,8 @@ 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.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.opengl.LodVertexBuffer;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.render.objects.GLVertexBuffer;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
@@ -44,10 +45,6 @@ import static com.seibel.lod.core.render.LodRenderer.EVENT_LOGGER;
*/
public class LodQuadBuilder
{
static final int MAX_BUFFER_SIZE = (1024 * 1024);
static final int QUAD_BYTE_SIZE = (12 * 4);
static final int MAX_QUADS_PER_BUFFER = MAX_BUFFER_SIZE / QUAD_BYTE_SIZE;
static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public final boolean skipQuadsWithZeroSkylight;
@@ -55,7 +52,7 @@ public class LodQuadBuilder
final ArrayList<BufferQuad>[] quads = (ArrayList<BufferQuad>[]) new ArrayList[6];
public static final int[][][] DIRECTION_VERTEX_QUAD = new int[][][]
public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][]
{
// X,Z //
{ // UP
@@ -103,7 +100,68 @@ public class LodQuadBuilder
{ 0, 0 }, // 3
},
};
public static final int[][][] DIRECTION_VERTEX_QUAD = new int[][][]
{
// X,Z //
{ // UP
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 1, 0 }, // 0
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
{ // DOWN
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
// X,Y //
{ // NORTH
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
{ // SOUTH
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 1, 0 }, // 0
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
// Z,Y //
{ // WEST
{ 0, 0 }, // 0
{ 1, 0 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 0, 1 }, // 3
},
{ // EAST
{ 0, 1 }, // 0
{ 1, 1 }, // 1
{ 1, 0 }, // 2
{ 0, 1 }, // 0
{ 1, 0 }, // 2
{ 0, 0 }, // 3
},
};
public LodQuadBuilder(boolean enableSkylightCulling, int skyLightCullingBelow)
@@ -176,7 +234,7 @@ public class LodQuadBuilder
private static void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte skylight, byte blocklight)
private static void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte skylight, byte blocklight, int mx, int my, int mz)
{
skylight %= 16;
blocklight %= 16;
@@ -184,8 +242,21 @@ public class LodQuadBuilder
bb.putShort(x);
bb.putShort(y);
bb.putShort(z);
bb.putShort((short) (skylight | (blocklight << 4)));
short meta = 0;
meta |= (skylight | (blocklight << 4));
byte mirco = 0;
// mirco offset which is a xyz 2bit value
// 0b00 = no offset
// 0b01 = positive offset
// 0b11 = negative offset
// format is: 0b00zzyyxx
if (mx != 0) mirco |= mx > 0 ? 0b01 : 0b11;
if (my != 0) mirco |= my > 0 ? 0b0100 : 0b1100;
if (mz != 0) mirco |= mz > 0 ? 0b010000 : 0b110000;
meta |= mirco << 8;
bb.putShort(meta);
byte r = (byte) ColorUtil.getRed(color);
byte g = (byte) ColorUtil.getGreen(color);
byte b = (byte) ColorUtil.getBlue(color);
@@ -198,35 +269,45 @@ public class LodQuadBuilder
private static void putQuad(ByteBuffer bb, BufferQuad quad)
{
int[][] quadBase = DIRECTION_VERTEX_QUAD[quad.direction.ordinal()];
int[][] quadBase = LodRenderer.ENABLE_IBO ? DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()] : DIRECTION_VERTEX_QUAD[quad.direction.ordinal()];
short widthEastWest = quad.widthEastWest;
short widthNorthSouth = quad.widthNorthSouthOrUpDown;
Axis axis = quad.direction.getAxis();
for (int i = 0; i < quadBase.length; i++)
{
short dx, dy, dz;
int mx, my, mz;
switch (axis)
{
case X: // ZY
dx = 0;
dy = quadBase[i][1] == 1 ? widthNorthSouth : 0;
dz = quadBase[i][0] == 1 ? widthEastWest : 0;
mx = 0;
my = quadBase[i][1] == 1 ? 1 : -1;
mz = quadBase[i][0] == 1 ? 1 : -1;
break;
case Y: // XZ
dx = quadBase[i][0] == 1 ? widthEastWest : 0;
dy = 0;
dz = quadBase[i][1] == 1 ? widthNorthSouth : 0;
mx = quadBase[i][0] == 1 ? 1 : -1;
my = 0;
mz = quadBase[i][1] == 1 ? 1 : -1;
break;
case Z: // XY
dx = quadBase[i][0] == 1 ? widthEastWest : 0;
dy = quadBase[i][1] == 1 ? widthNorthSouth : 0;
dz = 0;
mx = quadBase[i][0] == 1 ? 1 : -1;
my = quadBase[i][1] == 1 ? 1 : -1;
mz = 0;
break;
default:
throw new IllegalArgumentException("Invalid Axis enum: " + axis);
}
putVertex(bb, (short) (quad.x + dx), (short) (quad.y + dy), (short) (quad.z + dz), quad.color,
quad.skyLight, quad.blockLight);
quad.skyLight, quad.blockLight, mx, my, mz);
}
}
@@ -290,7 +371,7 @@ public class LodQuadBuilder
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(MAX_QUADS_PER_BUFFER * QUAD_BYTE_SIZE)
final ByteBuffer bb = ByteBuffer.allocateDirect(LodBufferBuilderFactory.FULL_SIZED_BUFFER)
.order(ByteOrder.nativeOrder());
int dir = skipEmpty(0);
int quad = 0;
@@ -316,7 +397,7 @@ public class LodQuadBuilder
return null;
}
bb.clear();
bb.limit(MAX_QUADS_PER_BUFFER * QUAD_BYTE_SIZE);
bb.limit(LodBufferBuilderFactory.FULL_SIZED_BUFFER);
while (bb.hasRemaining() && dir < 6)
{
writeData();
@@ -355,7 +436,7 @@ public class LodQuadBuilder
public interface BufferFiller
{
/** If true: more data needs to be filled */
boolean fill(LodVertexBuffer vbo);
boolean fill(GLVertexBuffer vbo);
}
public BufferFiller makeBufferFiller(GpuUploadMethod method)
@@ -365,34 +446,35 @@ public class LodQuadBuilder
int dir = 0;
int quad = 0;
public boolean fill(LodVertexBuffer vbo)
public boolean fill(GLVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.vertexCount = 0;
vbo.setVertexCount(0);
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > MAX_QUADS_PER_BUFFER)
numOfQuads = MAX_QUADS_PER_BUFFER;
if (numOfQuads > LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER)
numOfQuads = LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.vertexCount = 0;
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * QUAD_BYTE_SIZE, method, MAX_QUADS_PER_BUFFER * QUAD_BYTE_SIZE);
ByteBuffer bb = vbo.mapBuffer(numOfQuads * LodBufferBuilderFactory.QUADS_BYTE_SIZE, method,
LodBufferBuilderFactory.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * QUAD_BYTE_SIZE);
bb.limit(numOfQuads * LodBufferBuilderFactory.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer(method);
vbo.vertexCount = numOfQuads * 4;
vbo.unmapBuffer();
vbo.setVertexCount(LodRenderer.ENABLE_IBO ? numOfQuads*4 : numOfQuads*6);
return dir < 6;
}
@@ -444,11 +526,11 @@ public class LodQuadBuilder
i += qs.size();
return i;
}
/** Returns how many Buffers will be needed to render everything in this builder. */
public int getCurrentNeededVertexBufferCount()
{
return LodUtil.ceilDiv(getCurrentQuadsCount(), MAX_QUADS_PER_BUFFER);
return LodUtil.ceilDiv(getCurrentQuadsCount(), LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER);
}
}
@@ -1,211 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects.opengl;
import java.nio.ByteBuffer;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.util.UnitBytes;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL44;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.rendering.GLProxyContext;
import com.seibel.lod.core.render.GLProxy;
/**
* This is a container for a OpenGL
* VBO (Vertex Buffer Object).
*
* @author James Seibel
* @version 11-20-2021
*/
public class LodVertexBuffer implements AutoCloseable
{
/**
* When uploading to a buffer that is too small, recreate it this many times
* bigger than the upload payload
*/
public static final double BUFFER_EXPANSION_MULTIPLIER = 1.3;
public static int count = 0;
public int id;
public int vertexCount;
public boolean isBufferStorage;
public long size = 0;
public LodVertexBuffer(boolean isBufferStorage)
{
_create(isBufferStorage);
}
private void _create(boolean asBufferStorage) {
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 = asBufferStorage;
count++;
}
private void _destroy() {
if (GLProxy.getInstance().getGlContext() == GLProxyContext.PROXY_WORKER) {
GL32.glDeleteBuffers(this.id);
} else {
final int id = this.id;
GLProxy.getInstance().recordOpenGlCall(() -> GL32.glDeleteBuffers(id));
}
this.id = -1;
size = 0;
vertexCount = 0;
count--;
}
private void _uploadBufferStorage(ByteBuffer bb) {
if (!isBufferStorage) throw new IllegalStateException("Buffer is not bufferStorage but its trying to use bufferStorage upload method!");
int bbSize = bb.limit() - bb.position();
GL32.glDeleteBuffers(id);
id = GL32.glGenBuffers();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id);
GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, bb, 0);
size = bbSize;
}
// bufferData
// simplest/most compatible
private void _uploadData(ByteBuffer bb) {
if (isBufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use Data upload method!");
int bbSize = bb.limit() - bb.position();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, id);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, bb, GL32.GL_STATIC_DRAW);
size = bbSize;
}
// bufferSubData
// less stutter, low GPU usage?
private void _uploadSubData(ByteBuffer bb, int maxExpensionSize) {
if (isBufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use SubData upload method!");
int bbSize = bb.limit() - bb.position();
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;
}
GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, bb);
}
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+"!");
GLProxy.GL_LOGGER.debug("Uploading {} buffer with {} vertices.", new UnitBytes(bbSize), vertCount);
// If size is zero, just ignore it.
if (bbSize == 0) return;
boolean useBuffStorage = uploadMethod.useBufferStorage;
if (useBuffStorage != isBufferStorage) {
_destroy();
_create(useBuffStorage);
}
switch (uploadMethod) {
case AUTO:
throw new IllegalArgumentException("GpuUploadMethod AUTO must be resolved before call to uploadBuffer()!");
case BUFFER_STORAGE:
_uploadBufferStorage(bb);
break;
case DATA:
_uploadData(bb);
break;
case SUB_DATA:
_uploadSubData(bb, maxExpensionSize);
break;
default:
throw new IllegalArgumentException("Invalid GpuUploadMethod enum");
}
}
@Override
public void close()
{
if (this.id >= 0)
{
_destroy();
if (count==0) ApiShared.LOGGER.info("All LodVerrtexBuffer is freed.");
} else {
ApiShared.LOGGER.error("LodVertexBuffer double close!");
}
}
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;
}
@Override
public String toString() {
return (isBufferStorage ? "VertexBufferStorage" : "BufferStorage")+
"[vboId:"+id+", size:"+size+", vertCount:"+vertexCount+(isMapped?", MAPPED" : "")+"]";
}
}
@@ -1,106 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects.opengl;
import com.seibel.lod.core.api.ApiShared;
import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.GL11C.GL_UNSIGNED_INT;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL31.GL_COPY_WRITE_BUFFER;
/**
* Represents a OpenGL Index Buffer Object.
*
* @author Cotex
* @version 2022-4-13
*/
public class QuadIBO
{
/**
* Datatype of the stored indices
* (can be GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, GL_UNSIGNED_BYTE)
*/
public int type;
/** OpenGL ID of this IBO object */
int id;
/** Current capacity (in quads) */
int currentQuadCapacity;
/** Global IBO object, used for sharing the IBO for any draw calls */
public static QuadIBO GLOBAL = new QuadIBO();
public QuadIBO()
{
id = glGenBuffers();
}
/** Should only be called on a thread that has a OpenGL context. */
public void resizeIfNecessary(int newQuadCapacity)
{
//If the requested capacity is less than or equal to current capacity, ignore
if (newQuadCapacity <= currentQuadCapacity)
return;
// create the new capacity bigger than necessary to prevent constant updates
newQuadCapacity *= 1.5;
ApiShared.LOGGER.info("Quad IBO Resizing from [" + currentQuadCapacity + "] to [" + newQuadCapacity + "]");
currentQuadCapacity = newQuadCapacity;
//TODO: DO DYNAMIC TYPES, just makes things more efficient
type = GL_UNSIGNED_INT;
int DT_SIZE = 4; //Datatype size (int: 4, short: 2, byte: 1)
glBindBuffer(GL_COPY_WRITE_BUFFER, id);
//Resize the buffer
glBufferData(GL_COPY_WRITE_BUFFER, (long) DT_SIZE * 6 * newQuadCapacity, GL_STATIC_DRAW);// 4L is datatype
//Map and write the index data to the buffer
long arrayPointer = nglMapBuffer(GL_COPY_WRITE_BUFFER, GL_WRITE_ONLY);
for (int base = 0; base < newQuadCapacity; base++)
{
// Add the new quad's indices
MemoryUtil.memPutInt(arrayPointer + (base * 6L * DT_SIZE + DT_SIZE * 0), (int) (base * 4 + 0));
MemoryUtil.memPutInt(arrayPointer + (base * 6L * DT_SIZE + DT_SIZE * 1), (int) (base * 4 + 1));
MemoryUtil.memPutInt(arrayPointer + (base * 6L * DT_SIZE + DT_SIZE * 2), (int) (base * 4 + 2));
MemoryUtil.memPutInt(arrayPointer + (base * 6L * DT_SIZE + DT_SIZE * 3), (int) (base * 4 + 2));
MemoryUtil.memPutInt(arrayPointer + (base * 6L * DT_SIZE + DT_SIZE * 4), (int) (base * 4 + 3));
MemoryUtil.memPutInt(arrayPointer + (base * 6L * DT_SIZE + DT_SIZE * 5), (int) (base * 4 + 0));
}
glUnmapBuffer(GL_COPY_WRITE_BUFFER);
}
/** Binds the IBO */
public void bind()
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id);
}
/** Unbinds the IBO */
public void unbind()
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
}
@@ -23,7 +23,10 @@ import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.render.objects.GLVertexBuffer;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.api.ApiShared;
@@ -42,16 +45,14 @@ import static com.seibel.lod.core.render.GLProxy.GL_LOGGER;
public class SimpleRenderBuffer extends RenderBuffer
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final int FULL_SIZED_BUFFERS =
LodBufferBuilderFactory.MAX_TRIANGLES_PER_BUFFER * LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3;
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
LodVertexBuffer[] vbos;
GLVertexBuffer[] vbos;
// public void onReuse() {}
public SimpleRenderBuffer() {
vbos = new LodVertexBuffer[0];
vbos = new GLVertexBuffer[0];
}
@Override
@@ -74,14 +75,17 @@ public class SimpleRenderBuffer extends RenderBuffer
public boolean render(LodRenderProgram shaderProgram)
{
boolean hasRendered = false;
for (LodVertexBuffer vbo : vbos) {
for (GLVertexBuffer vbo : vbos) {
if (vbo == null) continue;
if (vbo.vertexCount == 0) continue;
if (vbo.getVertexCount() == 0) continue;
hasRendered = true;
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
shaderProgram.bindVertexBuffer(vbo.id);
QuadIBO.GLOBAL.resizeIfNecessary(vbo.vertexCount/4);
GL32.glDrawElements(GL32.GL_TRIANGLES, (vbo.vertexCount/4)*6, QuadIBO.GLOBAL.type, 0);
vbo.bind();
shaderProgram.bindVertexBuffer(vbo.getId());
if (LodRenderer.ENABLE_IBO) {
GL32.glDrawElements(GL32.GL_TRIANGLES, (vbo.getVertexCount()/4)*6, ClientApi.renderer.quadIBO.getType(), 0);
} else {
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbo.getVertexCount());
}
//LodRenderer.tickLogger.info("Vertex buffer: {}", vbo);
}
return hasRendered;
@@ -92,14 +96,14 @@ public class SimpleRenderBuffer extends RenderBuffer
{
statsMap.incStat("RenderBuffers");
statsMap.incStat("SimpleRenderBuffers");
for (LodVertexBuffer b : vbos) {
for (GLVertexBuffer b : vbos) {
if (b == null) continue;
statsMap.incStat("VBOs");
if (b.size == FULL_SIZED_BUFFERS) {
if (b.getSize() == LodBufferBuilderFactory.FULL_SIZED_BUFFER) {
statsMap.incStat("FullsizedVBOs");
}
if (b.size == 0) GL_LOGGER.warn("VBO with size 0");
statsMap.incBytesStat("TotalUsage", b.size);
if (b.getSize() == 0) GL_LOGGER.warn("VBO with size 0");
statsMap.incBytesStat("TotalUsage", b.getSize());
}
}
@@ -107,8 +111,8 @@ public class SimpleRenderBuffer extends RenderBuffer
public void close()
{
GLProxy.getInstance().recordOpenGlCall(() -> {
for (LodVertexBuffer b : vbos) {
b.close();
for (GLVertexBuffer b : vbos) {
b.destroy(false);
}
});
}
@@ -122,10 +126,11 @@ public class SimpleRenderBuffer extends RenderBuffer
Iterator<ByteBuffer> iter = builder.makeVertexBuffers();
while (iter.hasNext()) {
ByteBuffer bb = iter.next();
LodVertexBuffer vbo = getOrMakeVbo(i++, method.useBufferStorage);
GLVertexBuffer vbo = getOrMakeVbo(i++, method.useBufferStorage);
int size = bb.limit() - bb.position();
try {
vbo.uploadBuffer(bb, size/LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFERS);
vbo.bind();
vbo.uploadBuffer(bb, size/LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, LodBufferBuilderFactory.FULL_SIZED_BUFFER);
} catch (Exception e) {
vbos[i-1] = null;
vbo.close();
@@ -151,23 +156,23 @@ public class SimpleRenderBuffer extends RenderBuffer
{
resize(builder.getCurrentNeededVertexBufferCount());
for (int i=0; i<vbos.length; i++) {
if (vbos[i]==null) vbos[i] = new LodVertexBuffer(method.useBufferStorage);
if (vbos[i]==null) vbos[i] = new GLVertexBuffer(method.useBufferStorage);
}
BufferFiller func = builder.makeBufferFiller(method);
int i = 0;
while (i < vbos.length && func.fill(vbos[i++])) {}
}
private LodVertexBuffer getOrMakeVbo(int iIndex, boolean useBuffStorage) {
private GLVertexBuffer getOrMakeVbo(int iIndex, boolean useBuffStorage) {
if (vbos[iIndex] == null) {
vbos[iIndex] = new LodVertexBuffer(useBuffStorage);
vbos[iIndex] = new GLVertexBuffer(useBuffStorage);
}
return vbos[iIndex];
}
private void resize(int size) {
if (vbos.length != size) {
LodVertexBuffer[] newVbos = new LodVertexBuffer[size];
GLVertexBuffer[] newVbos = new GLVertexBuffer[size];
if (vbos.length > size) {
for (int i=size; i<vbos.length; i++) {
if (vbos[i]!=null) vbos[i].close();
@@ -178,7 +183,7 @@ public class SimpleRenderBuffer extends RenderBuffer
newVbos[i] = vbos[i];
vbos[i] = null;
}
for (LodVertexBuffer b : vbos) {
for (GLVertexBuffer b : vbos) {
if (b != null) throw new RuntimeException("LEAKING VBO!");
}
vbos = newVbos;
@@ -40,6 +40,8 @@ public class LodRenderProgram extends ShaderProgram {
public final int modelOffsetUniform;
public final int worldYOffsetUniform;
public final int mircoOffsetUniform;
public final int lightMapUniform;
// Fog Uniforms
public final int fogColorUniform;
@@ -61,6 +63,7 @@ public class LodRenderProgram extends ShaderProgram {
combinedMatUniform = getUniformLocation("combinedMatrix");
modelOffsetUniform = getUniformLocation("modelOffset");
worldYOffsetUniform = tryGetUniformLocation("worldYOffset");
mircoOffsetUniform = getUniformLocation("mircoOffset");
lightMapUniform = getUniformLocation("lightMap");
@@ -81,9 +84,9 @@ public class LodRenderProgram extends ShaderProgram {
vao = new VertexAttributePreGL43(); // also binds VertexAttribute
vao.bind();
// Now a pos+light.
vao.setVertexAttribute(0, 0, VertexAttribute.VertexPointer.addUnsignedShortsPointer(4, false)); // 2+2+2+2
vao.setVertexAttribute(0, 0, VertexAttribute.VertexPointer.addUnsignedShortsPointer(4, false, true)); // 2+2+2+2
//vao.setVertexAttribute(0, posAttrib, VertexAttribute.VertexPointer.addVec3Pointer(false)); // 4+4+4
vao.setVertexAttribute(0, 1, VertexAttribute.VertexPointer.addUnsignedBytesPointer(4, true)); // +4
vao.setVertexAttribute(0, 1, VertexAttribute.VertexPointer.addUnsignedBytesPointer(4, true, false)); // +4
//vao.setVertexAttribute(0, lightAttrib, VertexAttribute.VertexPointer.addUnsignedBytesPointer(2, false)); // +4 due to how it aligns
try {
vao.completeAndCheck(vertexByteCount);
@@ -132,6 +135,7 @@ public class LodRenderProgram extends ShaderProgram {
vanillaDrawDistance += 32; // Give it a 2 chunk boundary for near fog.
// uniforms
setUniform(combinedMatUniform, combinedMatrix);
setUniform(mircoOffsetUniform, 0.005f); // 0.005 block offset
// setUniform(skyLightUniform, skyLight);
setUniform(lightMapUniform, lightmapBindPoint);
@@ -26,10 +26,9 @@ import java.util.concurrent.TimeUnit;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.logging.ConfigBasedSpamLogger;
import com.seibel.lod.core.logging.SpamReducedLogger;
import com.seibel.lod.core.objects.BoolType;
import com.seibel.lod.core.objects.Pos2D;
import com.seibel.lod.core.objects.opengl.QuadIBO;
import com.seibel.lod.core.render.objects.QuadElementBuffer;
import com.seibel.lod.core.render.objects.GLState;
import com.seibel.lod.core.util.*;
import com.seibel.lod.core.util.gridList.*;
@@ -47,7 +46,6 @@ import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.objects.math.Vec3f;
import com.seibel.lod.core.objects.opengl.RenderRegion;
import com.seibel.lod.core.render.objects.LightmapTexture;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
@@ -73,6 +71,8 @@ public class LodRenderer
public static final boolean ENABLE_DRAW_LAG_SPIKE_LOGGING = false;
public static final boolean ENABLE_DUMP_GL_STATE = true;
public static final long DRAW_LAG_SPIKE_THRESHOLD_NS = TimeUnit.NANOSECONDS.convert(20, TimeUnit.MILLISECONDS);
public static final boolean ENABLE_IBO = true;
public static class LagSpikeCatcher {
@@ -108,6 +108,7 @@ public class LodRenderer
/** This is used to determine if the LODs should be regenerated */
private AbstractBlockPosWrapper lastUpdatedPos = null;
public QuadElementBuffer quadIBO = null;
// these variables are used to determine if the buffers should be rebuilt
private int prevRenderDistance = 0;
@@ -319,12 +320,15 @@ public class LodRenderer
LagSpikeCatcher drawFillLightmap = new LagSpikeCatcher();
ILightMapWrapper lightmap = MC_RENDER.getLightmapWrapper();
lightmap.bind();
QuadIBO.GLOBAL.bind();
if (ENABLE_IBO) quadIBO.bind();
//lightmapTexture.fillData(MC_RENDER.getLightmapTextureWidth(), MC_RENDER.getLightmapTextureHeight(), MC_RENDER.getLightmapPixels());
drawFillLightmap.end("drawFillLightmap");
drawFillData.end("DrawFillData");
//GL32.glEnable( GL32.GL_POLYGON_OFFSET_FILL );
//GL32.glPolygonOffset( 1f, 1f );
//===========//
// rendering //
//===========//
@@ -376,7 +380,7 @@ public class LodRenderer
profiler.popPush("LOD cleanup");
LagSpikeCatcher drawCleanup = new LagSpikeCatcher();
lightmap.unbind();
QuadIBO.GLOBAL.unbind();
if (ENABLE_IBO) quadIBO.unbind();
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
@@ -410,6 +414,10 @@ public class LodRenderer
EVENT_LOGGER.info("Setting up renderer");
isSetupComplete = true;
shaderProgram = new LodRenderProgram(LodFogConfig.generateFogConfig());
if (ENABLE_IBO) {
quadIBO = new QuadElementBuffer();
quadIBO.reserve(LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER);
}
EVENT_LOGGER.info("Renderer setup complete");
}
@@ -492,6 +500,7 @@ public class LodRenderer
isSetupComplete = false;
EVENT_LOGGER.info("Renderer Cleanup Started");
shaderProgram.free();
if (quadIBO != null) quadIBO.destroy(false);
EVENT_LOGGER.info("Renderer Cleanup Complete");
}
@@ -25,7 +25,7 @@ import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.logging.ConfigBasedSpamLogger;
import com.seibel.lod.core.objects.opengl.LodVertexBuffer;
import com.seibel.lod.core.render.objects.GLVertexBuffer;
import com.seibel.lod.core.render.objects.GLState;
import com.seibel.lod.core.render.objects.ShaderProgram;
import com.seibel.lod.core.render.objects.VertexAttribute;
@@ -48,8 +48,8 @@ public class RenderSystemTest {
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
ShaderProgram basicShader;
LodVertexBuffer sameContextBuffer;
LodVertexBuffer sharedContextBuffer;
GLVertexBuffer sameContextBuffer;
GLVertexBuffer sharedContextBuffer;
VertexAttribute va;
boolean init = false;
@@ -77,8 +77,8 @@ public class RenderSystemTest {
-0.2f, 0.2f, 0.0f, 1.0f, 1.0f, 1.0f
};
private static LodVertexBuffer createTextingBuffer() {
LodVertexBuffer vbo = new LodVertexBuffer(false);
private static GLVertexBuffer createTextingBuffer() {
GLVertexBuffer vbo = new GLVertexBuffer(false);
ByteBuffer buffer = ByteBuffer.allocateDirect(vertices.length * Float.BYTES);
// Fill buffer with the vertices.
buffer = buffer.order(ByteOrder.nativeOrder());
@@ -113,12 +113,12 @@ public class RenderSystemTest {
// Switch between the two buffers per second
if (System.currentTimeMillis() % 2000 < 1000) {
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, sameContextBuffer.id);
va.bindBufferToAllBindingPoint(sameContextBuffer.id);
sameContextBuffer.bind();
va.bindBufferToAllBindingPoint(sameContextBuffer.getId());
spamLogger.debug("same context buffer");
} else {
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, sharedContextBuffer.id);
va.bindBufferToAllBindingPoint(sharedContextBuffer.id);
sameContextBuffer.bind();
va.bindBufferToAllBindingPoint(sharedContextBuffer.getId());
spamLogger.debug("shared context buffer");
}
// Render the square
@@ -0,0 +1,188 @@
package com.seibel.lod.core.render.objects;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.rendering.GLProxyContext;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.util.UnitBytes;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL44;
import java.nio.ByteBuffer;
public class GLBuffer implements AutoCloseable {
public static final double BUFFER_EXPANSION_MULTIPLIER = 1.3;
public static final double BUFFER_SHRINK_TRIGGER = BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER;
public static int count = 0;
protected int id;
public final int getId() {
return id;
}
protected int size = 0;
public int getSize() {
return size;
}
protected boolean bufferStorage;
public final boolean isBufferStorage() {
return bufferStorage;
}
protected boolean isMapped = false;
public GLBuffer(boolean isBufferStorage)
{
create(isBufferStorage);
}
// Should be override by subclasses
public int getBufferBindingTarget() {
return GL32.GL_COPY_READ_BUFFER;
}
public void bind() {
GL32.glBindBuffer(getBufferBindingTarget(), id);
}
public void unbind() {
GL32.glBindBuffer(getBufferBindingTarget(), 0);
}
protected void create(boolean asBufferStorage) {
if (GLProxy.getInstance().getGlContext() == GLProxyContext.NONE)
throw new IllegalStateException("Thread [" +Thread.currentThread().getName() + "] tried to create a GLBuffer outside a OpenGL context.");
this.id = GL32.glGenBuffers();
this.bufferStorage = asBufferStorage;
count++;
}
protected void destroy(boolean async) {
if (this.id == 0) {
ApiShared.LOGGER.warn("Buffer double close!");
return;
}
if (async && GLProxy.getInstance().getGlContext() != GLProxyContext.PROXY_WORKER) {
GLProxy.getInstance().recordOpenGlCall(() -> destroy((false)));
} else {
GL32.glDeleteBuffers(id);
}
id = 0;
size = 0;
count--;
if (count==0) ApiShared.LOGGER.info("All GLBuffer is freed.");
}
// Requires already binded
protected void uploadBufferStorage(ByteBuffer bb, int bufferStorageHint) {
if (!bufferStorage) throw new IllegalStateException("Buffer is not bufferStorage but its trying to use bufferStorage upload method!");
int bbSize = bb.limit() - bb.position();
destroy(false);
create(true);
bind();
GL44.glBufferStorage(getBufferBindingTarget(), bb, bufferStorageHint);
size = bbSize;
}
// Requires already binded
protected void uploadBufferData(ByteBuffer bb, int bufferDataHint) {
if (bufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use Data upload method!");
int bbSize = bb.limit() - bb.position();
GL32.glBufferData(getBufferBindingTarget(), bb, bufferDataHint);
size = bbSize;
}
// Requires already binded
protected void uploadSubData(ByteBuffer bb, int maxExpansionSize, int bufferDataHint) {
if (bufferStorage) throw new IllegalStateException("Buffer is bufferStorage but its trying to use SubData upload method!");
int bbSize = bb.limit() - bb.position();
if (size < bbSize || size > bbSize * BUFFER_SHRINK_TRIGGER) {
int newSize = (int) (bbSize * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxExpansionSize) newSize = maxExpansionSize;
GL32.glBufferData(getBufferBindingTarget(), newSize, bufferDataHint);
size = newSize;
}
GL32.glBufferSubData(getBufferBindingTarget(), 0, bb);
}
// Requires already binded
public void uploadBuffer(ByteBuffer bb, GpuUploadMethod uploadMethod, int maxExpansionSize, int bufferHint) {
if (uploadMethod.useEarlyMapping)
throw new IllegalArgumentException("UploadMethod signal that this should use Mapping instead of uploadBuffer!");
int bbSize = bb.limit()-bb.position();
if (bbSize > maxExpansionSize)
throw new IllegalArgumentException("maxExpansionSize is "+maxExpansionSize+" but buffer size is "+bbSize+"!");
GLProxy.GL_LOGGER.debug("Uploading buffer with {}.", new UnitBytes(bbSize));
// If size is zero, just ignore it.
if (bbSize == 0) return;
boolean useBuffStorage = uploadMethod.useBufferStorage;
if (useBuffStorage != bufferStorage) {
destroy(false);
create(useBuffStorage);
bind();
}
switch (uploadMethod) {
case AUTO:
throw new IllegalArgumentException("GpuUploadMethod AUTO must be resolved before call to uploadBuffer()!");
case BUFFER_STORAGE:
uploadBufferStorage(bb, bufferHint);
break;
case DATA:
uploadBufferData(bb, bufferHint);
break;
case SUB_DATA:
uploadSubData(bb, maxExpansionSize, bufferHint);
break;
default:
throw new IllegalArgumentException("Invalid GpuUploadMethod enum");
}
}
public ByteBuffer mapBuffer(int targetSize, GpuUploadMethod uploadMethod, int maxExpensionSize, int bufferHint, int mapFlags) {
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 != bufferStorage) {
destroy(false);
create(useBuffStorage);
}
bind();
ByteBuffer vboBuffer;
if (size < targetSize || size > targetSize * BUFFER_SHRINK_TRIGGER) {
int newSize = (int) (targetSize * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxExpensionSize) newSize = maxExpensionSize;
size = newSize;
if (bufferStorage) {
GL32.glDeleteBuffers(id);
id = GL32.glGenBuffers();
GL32.glBindBuffer(getBufferBindingTarget(), id);
GL44.glBufferStorage(getBufferBindingTarget(), newSize, bufferHint);
} else {
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, bufferHint);
}
}
vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, targetSize, mapFlags);
isMapped = true;
return vboBuffer;
}
// Requires already binded
public void unmapBuffer()
{
if (!isMapped) throw new IllegalStateException("Unmap Buffer called but buffer is already not mapped!");
bind();
GL32.glUnmapBuffer(getBufferBindingTarget());
isMapped = false;
}
@Override
public void close()
{
destroy(true);
}
@Override
public String toString() {
return (bufferStorage ? "" : "Static-")+ getClass().getSimpleName() +
"[id:"+id+",size:"+size+(isMapped?",MAPPED" : "")+"]";
}
}
@@ -0,0 +1,60 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.render.objects;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import org.lwjgl.opengl.GL32;
import java.nio.ByteBuffer;
/**
* This is a container for a OpenGL
* VBO (Vertex Buffer Object).
*
* @author James Seibel
* @version 11-20-2021
*/
public class GLElementBuffer extends GLBuffer
{
/**
* When uploading to a buffer that is too small, recreate it this many times
* bigger than the upload payload
*/
protected int indicesCount = 0;
public int getIndicesCount() { return indicesCount; }
protected int type = GL32.GL_UNSIGNED_INT;
public int getType() { return type; }
public GLElementBuffer(boolean isBufferStorage)
{
super(isBufferStorage);
}
@Override
public void destroy(boolean async) {
super.destroy(async);
indicesCount = 0;
}
@Override
public int getBufferBindingTarget() {
return GL32.GL_ELEMENT_ARRAY_BUFFER;
}
}
@@ -204,4 +204,24 @@ public class GLEnums {
return "GL_UNKNOWN(" + glEnum + ")";
}
public static int getTypeSize(int glTypeEnum) {
switch (glTypeEnum) {
case GL_BYTE:
case GL_UNSIGNED_BYTE:
return 1;
case GL_SHORT:
case GL_UNSIGNED_SHORT:
return 2;
case GL_INT:
case GL_UNSIGNED_INT:
return 4;
case GL_FLOAT:
return 4;
case GL_DOUBLE:
return 8;
default:
throw new IllegalArgumentException("Unknown type enum: " + getString(glTypeEnum));
}
}
}
@@ -26,6 +26,7 @@ public class GLState {
public int prog;
public int vao;
public int vbo;
public int ebo;
public int fbo;
public int text;
public int activeTex;
@@ -48,6 +49,7 @@ public class GLState {
prog = GL32.glGetInteger(GL32.GL_CURRENT_PROGRAM);
vao = GL32.glGetInteger(GL32.GL_VERTEX_ARRAY_BINDING);
vbo = GL32.glGetInteger(GL32.GL_ARRAY_BUFFER_BINDING);
ebo = GL32.glGetInteger(GL32.GL_ELEMENT_ARRAY_BUFFER_BINDING);
fbo = GL32.glGetInteger(GL32.GL_FRAMEBUFFER_BINDING);
text = GL32.glGetInteger(GL32.GL_TEXTURE_BINDING_2D);
activeTex = GL32.glGetInteger(GL32.GL_ACTIVE_TEXTURE);
@@ -72,7 +74,7 @@ public class GLState {
@Override
public String toString() {
return "GLState{" + "prog=" + prog + ", vao=" + vao + ", vbo=" + vbo + ", fbo=" + fbo +
return "GLState{" + "prog=" + prog + ", vao=" + vao + ", vbo=" + vbo + ", ebo=" + ebo + ", fbo=" + fbo +
", text=" + GLEnums.getString(text) + "@"+activeTex+", text0=" + GLEnums.getString(text0) +
", blend=" + blend + ", blendMode=" + GLEnums.getString(blendSrc) + "," + GLEnums.getString(blendDst) +
", depth=" + depth +
@@ -96,6 +98,7 @@ public class GLState {
GL32.glBindTexture(GL32.GL_TEXTURE_2D, text);
GL32.glBindVertexArray(vao);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo);
GL32.glBindBuffer(GL32.GL_ELEMENT_ARRAY_BUFFER, ebo);
GL32.glBlendFunc(blendSrc, blendDst);
if (depth) {
@@ -0,0 +1,78 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.render.objects;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
/**
* This is a container for a OpenGL
* VBO (Vertex Buffer Object).
*
* @author James Seibel
* @version 11-20-2021
*/
public class GLVertexBuffer extends GLBuffer
{
/**
* When uploading to a buffer that is too small, recreate it this many times
* bigger than the upload payload
*/
protected int vertexCount = 0;
public int getVertexCount() { return vertexCount; }
// FIXME: This setter is needed for premapping buffer to manually set the vertexCount. Fix this.
public void setVertexCount(int vertexCount) { this.vertexCount = vertexCount; }
public GLVertexBuffer(boolean isBufferStorage)
{
super(isBufferStorage);
}
@Override
public void destroy(boolean async) {
super.destroy(async);
vertexCount = 0;
}
@Override
public int getBufferBindingTarget() {
return GL32.GL_ARRAY_BUFFER;
}
public void uploadBuffer(ByteBuffer bb, int vertCount, GpuUploadMethod uploadMethod, int maxExpensionSize) {
if (vertCount < 0) throw new IllegalArgumentException("VertCount is negative!");
// If size is zero, just ignore it.
if (bb.limit()-bb.position() != 0) {
boolean useBuffStorage = uploadMethod.useBufferStorage;
super.uploadBuffer(bb, uploadMethod, maxExpensionSize, useBuffStorage ? 0 : GL32.GL_STATIC_DRAW);
}
vertexCount = vertCount;
}
public ByteBuffer mapBuffer(int targetSize, GpuUploadMethod uploadMethod, int maxExpensionSize)
{
return super.mapBuffer(targetSize, uploadMethod, maxExpensionSize,
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);
}
}
@@ -0,0 +1,126 @@
package com.seibel.lod.core.render.objects;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.render.GLProxy;
import org.lwjgl.opengl.GL32;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class QuadElementBuffer extends GLElementBuffer {
public QuadElementBuffer() {
super(GLProxy.getInstance().bufferStorageSupported);
}
public int getCapacity() {
return super.getSize() / GLEnums.getTypeSize(getType());
}
private static void buildBufferByte(int quadCount, ByteBuffer buffer) {
for (int i = 0; i < quadCount; i++) {
int vIndex = i * 4;
// First triangle
buffer.put((byte) (vIndex));
buffer.put((byte) (vIndex + 1));
buffer.put((byte) (vIndex + 2));
// Second triangle
buffer.put((byte) (vIndex + 2));
buffer.put((byte) (vIndex + 3));
buffer.put((byte) (vIndex));
}
if (buffer.hasRemaining()) {
throw new IllegalStateException("QuadElementBuffer is not full somehow after building");
}
buffer.rewind();
}
private static void buildBufferShort(int quadCount, ByteBuffer buffer) {
for (int i = 0; i < quadCount; i++) {
int vIndex = i * 4;
// First triangle
buffer.putShort((short) (vIndex));
buffer.putShort((short) (vIndex + 1));
buffer.putShort((short) (vIndex + 2));
// Second triangle
buffer.putShort((short) (vIndex + 2));
buffer.putShort((short) (vIndex + 3));
buffer.putShort((short) (vIndex));
}
if (buffer.hasRemaining()) {
throw new IllegalStateException("QuadElementBuffer is not full somehow after building");
}
buffer.rewind();
}
private static void buildBufferInt(int quadCount, ByteBuffer buffer) {
for (int i = 0; i < quadCount; i++) {
int vIndex = i * 4;
// First triangle
buffer.putInt(vIndex);
buffer.putInt(vIndex + 1);
buffer.putInt(vIndex + 2);
// Second triangle
buffer.putInt(vIndex + 2);
buffer.putInt(vIndex + 3);
buffer.putInt(vIndex);
}
if (buffer.hasRemaining()) {
throw new IllegalStateException("QuadElementBuffer is not full somehow after building");
}
buffer.rewind();
}
private static void buildBuffer(int quadCount, ByteBuffer buffer, int type) {
switch (type) {
case GL32.GL_UNSIGNED_BYTE:
buildBufferByte(quadCount, buffer);
break;
case GL32.GL_UNSIGNED_SHORT:
buildBufferShort(quadCount, buffer);
break;
case GL32.GL_UNSIGNED_INT:
buildBufferInt(quadCount, buffer);
break;
default:
throw new IllegalStateException("Unknown type: " + type);
}
}
public void reserve(int quadCount) {
if (quadCount < 0) {
throw new IllegalArgumentException("quadCount must be greater than 0");
}
indicesCount = quadCount * 6; // 2 triangles per quad
if (indicesCount >= getCapacity() && indicesCount < getCapacity() * BUFFER_SHRINK_TRIGGER) {
return;
}
int vertexCount = quadCount * 4; // 4 vertices per quad
GLProxy gl = GLProxy.getInstance();
if (vertexCount < 255) { // Reserve 1 for the reset index
type = GL32.GL_UNSIGNED_BYTE;
} else if (vertexCount < 65535) { // Reserve 1 for the reset index
type = GL32.GL_UNSIGNED_SHORT;
} else {
type = GL32.GL_UNSIGNED_INT;
}
ApiShared.LOGGER.info("Quad IBO Resizing from [" + getCapacity() + "] to [" + quadCount + "]" + " with type: " +
GLEnums.getString(type));
ByteBuffer buffer;
if (!gl.bufferStorageSupported) {
bind();
buffer = super.mapBuffer(indicesCount * GLEnums.getTypeSize(type), GpuUploadMethod.BUFFER_MAPPING,
indicesCount * GLEnums.getTypeSize(type), GL32.GL_STATIC_DRAW,
GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT);
buildBuffer(quadCount, buffer, type);
super.unmapBuffer();
} else {
buffer = ByteBuffer.allocateDirect(indicesCount * GLEnums.getTypeSize(type)).order(ByteOrder.nativeOrder());
buildBuffer(quadCount, buffer, type);
bind();
super.uploadBuffer(buffer, GpuUploadMethod.BUFFER_STORAGE,
indicesCount * GLEnums.getTypeSize(type), 0);
}
}
}
@@ -31,11 +31,16 @@ public abstract class VertexAttribute {
public final int glType;
public final boolean normalized;
public final int byteSize;
public VertexPointer(int elementCount, int glType, boolean normalized, int byteSize) {
public final boolean useInteger;
public VertexPointer(int elementCount, int glType, boolean normalized, int byteSize, boolean useInteger) {
this.elementCount = elementCount;
this.glType = glType;
this.normalized = normalized;
this.byteSize = byteSize;
this.useInteger = useInteger;
}
public VertexPointer(int elementCount, int glType, boolean normalized, int byteSize) {
this(elementCount, glType, normalized, byteSize, false);
}
private static int _align(int bytes) {
return LodUtil.ceilDiv(bytes, 4)*4;
@@ -53,26 +58,29 @@ public abstract class VertexAttribute {
public static VertexPointer addVec4Pointer(boolean normalized) {
return new VertexPointer(4, GL32.GL_FLOAT, normalized, 16);
}
public static VertexPointer addUnsignedBytePointer(boolean normalized) {
return new VertexPointer(1, GL32.GL_UNSIGNED_BYTE, normalized, 4); // Always aligned to 4 bytes
public static VertexPointer addUnsignedBytePointer(boolean normalized, boolean useInteger) {
return new VertexPointer(1, GL32.GL_UNSIGNED_BYTE, normalized, 4, useInteger); // Always aligned to 4 bytes
}
public static VertexPointer addUnsignedBytesPointer(int elementCount, boolean normalized) {
return new VertexPointer(elementCount, GL32.GL_UNSIGNED_BYTE, normalized, _align(elementCount)); // aligned to 4 bytes
public static VertexPointer addUnsignedBytesPointer(int elementCount, boolean normalized, boolean useInteger) {
return new VertexPointer(elementCount, GL32.GL_UNSIGNED_BYTE, normalized, _align(elementCount), useInteger); // aligned to 4 bytes
}
public static VertexPointer addUnsignedShortsPointer(int elementCount, boolean normalized) {
return new VertexPointer(elementCount, GL32.GL_UNSIGNED_SHORT, normalized, _align(elementCount*2));
public static VertexPointer addUnsignedShortsPointer(int elementCount, boolean normalized, boolean useInteger) {
return new VertexPointer(elementCount, GL32.GL_UNSIGNED_SHORT, normalized, _align(elementCount*2), useInteger);
}
public static VertexPointer addIntPointer(boolean normalized) {
return new VertexPointer(1, GL32.GL_INT, normalized, 4);
public static VertexPointer addShortsPointer(int elementCount, boolean normalized, boolean useInteger) {
return new VertexPointer(elementCount, GL32.GL_SHORT, normalized, _align(elementCount*2), useInteger);
}
public static VertexPointer addIvec2Pointer(boolean normalized) {
return new VertexPointer(2, GL32.GL_INT, normalized, 8);
public static VertexPointer addIntPointer(boolean normalized, boolean useInteger) {
return new VertexPointer(1, GL32.GL_INT, normalized, 4, useInteger);
}
public static VertexPointer addIvec3Pointer(boolean normalized) {
return new VertexPointer(3, GL32.GL_INT, normalized, 12);
public static VertexPointer addIvec2Pointer(boolean normalized, boolean useInteger) {
return new VertexPointer(2, GL32.GL_INT, normalized, 8, useInteger);
}
public static VertexPointer addIvec4Pointer(boolean normalized) {
return new VertexPointer(4, GL32.GL_INT, normalized, 16);
public static VertexPointer addIvec3Pointer(boolean normalized, boolean useInteger) {
return new VertexPointer(3, GL32.GL_INT, normalized, 12, useInteger);
}
public static VertexPointer addIvec4Pointer(boolean normalized, boolean useInteger) {
return new VertexPointer(4, GL32.GL_INT, normalized, 16, useInteger);
}
}
@@ -70,7 +70,10 @@ public final class VertexAttributePostGL43 extends VertexAttribute {
@Override
// Requires VertexAttribute binded
public void setVertexAttribute(int bindingPoint, int attributeIndex, VertexPointer attribute) {
GL43.glVertexAttribFormat(attributeIndex, attribute.elementCount, attribute.glType,
if (attribute.useInteger)
GL43.glVertexAttribIFormat(attributeIndex, attribute.elementCount, attribute.glType, strideSize);
else
GL43.glVertexAttribFormat(attributeIndex, attribute.elementCount, attribute.glType,
attribute.normalized, strideSize); // Here strideSize is new attrib offset
strideSize += attribute.byteSize;
if (numberOfBindingPoints <= bindingPoint) numberOfBindingPoints = bindingPoint+1;
@@ -59,7 +59,10 @@ public final class VertexAttributePreGL43 extends VertexAttribute {
for (int i=0; i< pointers.length; i++) {
VertexPointer pointer = pointers[i];
if (pointer==null) continue;
GL32.glVertexAttribPointer(i, pointer.elementCount, pointer.glType,
if (pointer.useInteger)
GL32.glVertexAttribIPointer(i, pointer.elementCount, pointer.glType,
strideSize, pointersOffset[i]);
else GL32.glVertexAttribPointer(i, pointer.elementCount, pointer.glType,
pointer.normalized, strideSize, pointersOffset[i]);
}
}
@@ -77,7 +80,10 @@ public final class VertexAttributePreGL43 extends VertexAttribute {
VertexPointer pointer = pointers[j];
if (pointer == null)
continue;
GL32.glVertexAttribPointer(j, pointer.elementCount, pointer.glType,
if (pointer.useInteger)
GL32.glVertexAttribIPointer(j, pointer.elementCount, pointer.glType,
strideSize, pointersOffset[j]);
else GL32.glVertexAttribPointer(j, pointer.elementCount, pointer.glType,
pointer.normalized, strideSize, pointersOffset[j]);
}
+22 -4
View File
@@ -1,6 +1,6 @@
#version 150 core
in vec4 vPosition;
in uvec4 vPosition;
in vec4 color;
out vec4 vertexColor;
@@ -13,6 +13,7 @@ uniform float worldYOffset;
uniform int worldSkyLight;
uniform sampler2D lightMap;
uniform float mircoOffset;
/**
@@ -27,11 +28,28 @@ uniform sampler2D lightMap;
void main()
{
vertexWorldPos = vPosition.xyz + modelOffset;
vertexYPos = vPosition.y + worldYOffset;
float light2 = (mod(vPosition.a, 16)+0.5) / 16.0;
float light = (floor(vPosition.a/16)+0.5) / 16.0;
uint meta = vPosition.a;
uint mirco = (meta & 0xFF00u) >> 8u; // mirco offset which is a xyz 2bit value
// 0b00 = no offset
// 0b01 = positive offset
// 0b11 = negative offset
// format is: 0b00zzyyxx
float mx = (mirco & 1u)!=0u ? mircoOffset : 0.0;
mx = (mirco & 2u)!=0u ? -mx : mx;
float my = (mirco & 4u)!=0u ? mircoOffset : 0.0;
my = (mirco & 8u)!=0u ? -my : my;
float mz = (mirco & 16u)!=0u ? mircoOffset : 0.0;
mz = (mirco & 32u)!=0u ? -mz : mz;
uint lights = meta & 0xFFu;
float light2 = (mod(float(lights), 16.0)+0.5) / 16.0;
float light = (float(lights/16u)+0.5) / 16.0;
vertexColor = color * vec4(texture(lightMap, vec2(light, light2)).xyz, 1.0);
gl_Position = combinedMatrix * vec4(vertexWorldPos, 1.0);
gl_Position = combinedMatrix * vec4(vertexWorldPos + vec3(mx, my, mz), 1.0);
}