Clean up transparency and buffer render orders.

This commit is contained in:
TomTheFurry
2022-09-16 17:53:54 +08:00
parent 690dd319cb
commit fc5cb6796b
14 changed files with 606 additions and 460 deletions
@@ -472,7 +472,10 @@ public class Mat4f
this.m33,
};
}
public Vec3f asNonNormalizedLookForwardVector() {
return new Vec3f(this.m02, this.m12, this.m22);
}
//===============//
// Forge methods //
@@ -17,18 +17,16 @@ public interface LodRenderSource {
void enableRender(IClientLevel level, LodQuadTree quadTree);
void disableRender();
boolean isRenderReady();
void dispose(); // notify the container that the parent lodSection is now disposed (can be in loaded or unloaded state)
/**
* Try and swap in new render buffer for this section. Note that before this call, there should be no other
* places storing or referencing the render buffer.
* @param referenceSlotsOpaque The opaque slot for swapping in the new buffer.
* @param referenceSlotsTransparent The transparent slot for swapping in the new buffer.
* @param referenceSlot The slot for swapping in the new buffer.
* @return True if the swap was successful. False if swap is not needed or if it is in progress.
*/
boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference<RenderBuffer> referenceSlotsOpaque, AtomicReference<RenderBuffer> referenceSlotsTransparent);
boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference<RenderBuffer> referenceSlot);
void saveRender(IClientLevel level, RenderMetaFile file, OutputStream dataStream) throws IOException;
@@ -34,15 +34,10 @@ public class PlaceHolderRenderSource implements LodRenderSource {
@Override
public void disableRender() {}
@Override
public boolean isRenderReady() {
return false;
}
@Override
public void dispose() {}
@Override
public boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference<RenderBuffer> referenceSlotsOpaque, AtomicReference<RenderBuffer> referenceSlotsTransparent) {
public boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference<RenderBuffer> referenceSlots) {
return false;
}
@Override
@@ -265,9 +265,8 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
return SECTION_SIZE_OFFSET;
}
private CompletableFuture<ColumnRenderBuffer[]> inBuildRenderBuffer = null;
private Reference<ColumnRenderBuffer> usedBufferOpaque = new Reference<>();
private Reference<ColumnRenderBuffer> usedBufferTransparent = new Reference<>();
private CompletableFuture<ColumnRenderBuffer> inBuildRenderBuffer = null;
private Reference<ColumnRenderBuffer> usedBuffer = new Reference<>();
private void tryBuildBuffer(IClientLevel level, LodQuadTree quadTree) {
@@ -279,7 +278,7 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
data[direction.ordinal()-2] = ((ColumnRenderSource) section.getRenderSource());
}
}
inBuildRenderBuffer = ColumnRenderBuffer.build(level, usedBufferOpaque, usedBufferTransparent, this, data);
inBuildRenderBuffer = ColumnRenderBuffer.build(level, usedBuffer, this, data);
}
}
private void cancelBuildBuffer() {
@@ -302,11 +301,6 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
cancelBuildBuffer();
}
@Override
public boolean isRenderReady() {
return inBuildRenderBuffer == null || inBuildRenderBuffer.isDone();
}
@Override
public void dispose() {
cancelBuildBuffer();
@@ -318,7 +312,7 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
private static final long SWAP_BUSY_COLLISION_TIMEOUT = /* 1 sec */ 1_000_000_000L;
@Override
public boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference<RenderBuffer> referenceSlotsOpaque, AtomicReference<RenderBuffer> referenceSlotsTransparent) {
public boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference<RenderBuffer> referenceSlot) {
if (lastNs != -1 && System.nanoTime() - lastNs < SWAP_TIMEOUT) {
return false;
}
@@ -326,29 +320,12 @@ public class ColumnRenderSource implements LodRenderSource, IColumnDatatype {
if (inBuildRenderBuffer.isDone()) {
lastNs = System.nanoTime();
//LOGGER.info("Swapping render buffer for {}", sectionPos);
RenderBuffer[] newBuffers = inBuildRenderBuffer.join();
RenderBuffer oldBuffersOpaque = referenceSlotsOpaque.getAndSet(newBuffers[0]);
ColumnRenderBuffer swapped;
if (oldBuffersOpaque instanceof ColumnRenderBuffer) {
swapped = usedBufferOpaque.swap((ColumnRenderBuffer) oldBuffersOpaque);
RenderBuffer newBuffer = inBuildRenderBuffer.join();
RenderBuffer oldBuffer = referenceSlot.getAndSet(newBuffer);
if (oldBuffer instanceof ColumnRenderBuffer) {
ColumnRenderBuffer swapped = usedBuffer.swap((ColumnRenderBuffer) oldBuffer);
LodUtil.assertTrue(swapped == null);
}
if(LodRenderer.transparencyEnabled) {
RenderBuffer oldBuffersTransparent = referenceSlotsTransparent.getAndSet(newBuffers[1]);
if (LodRenderer.transparencyEnabled) {
if (oldBuffersTransparent instanceof ColumnRenderBuffer) {
swapped = usedBufferTransparent.swap((ColumnRenderBuffer) oldBuffersTransparent);
LodUtil.assertTrue(swapped == null);
}
}
}
inBuildRenderBuffer = null;
return true;
}
@@ -42,27 +42,25 @@ public class ColumnRenderBuffer extends RenderBuffer {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
GLVertexBuffer[] vbos;
GLVertexBuffer[] vbosTransparent;
public final DhBlockPos pos;
public ColumnRenderBuffer(DhBlockPos pos) {
this.pos = pos;
vbos = new GLVertexBuffer[0];
vbosTransparent = new GLVertexBuffer[0];
}
private void _uploadBuffersDirect(LodQuadBuilder builder, EGpuUploadMethod method) throws InterruptedException {
resize(builder.getCurrentNeededVertexBufferCount());
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;
Iterator<ByteBuffer> iter = builder.makeVertexBuffers();
while (iter.hasNext()) {
if (i >= vbos.length) {
throw new RuntimeException("Too many vertex buffers!!");
}
ByteBuffer bb = iter.next();
GLVertexBuffer vbo = getOrMakeVbo(i++, method.useBufferStorage);
GLVertexBuffer vbo = getOrMake(vbos, i++, method.useBufferStorage);
int size = bb.limit() - bb.position();
try {
vbo.bind();
@@ -88,44 +86,59 @@ public class ColumnRenderBuffer extends RenderBuffer {
}
}
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)
{
resize(builder.getCurrentNeededVertexBufferCount());
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.makeBufferFiller(method);
int i = 0;
while (i < vbos.length && func.fill(vbos[i++])) {}
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 GLVertexBuffer getOrMakeVbo(int iIndex, boolean useBuffStorage) {
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];
}
private void resize(int size) {
if (vbos.length != 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();
vbos[i] = null;
}
}
for (int i=0; i<newVbos.length && i<vbos.length; i++) {
newVbos[i] = vbos[i];
vbos[i] = null;
}
for (GLVertexBuffer b : vbos) {
if (b != null) throw new RuntimeException("LEAKING VBO!");
}
vbos = newVbos;
}
}
public void uploadBuffer(LodQuadBuilder builder, EGpuUploadMethod method) throws InterruptedException {
if (method.useEarlyMapping) {
_uploadBuffersMapped(builder, method);
@@ -135,7 +148,7 @@ public class ColumnRenderBuffer extends RenderBuffer {
}
@Override
public boolean render(LodRenderer renderContext) {
public boolean renderOpaque(LodRenderer renderContext) {
boolean hasRendered = false;
renderContext.setupOffset(pos);
for (GLVertexBuffer vbo : vbos) {
@@ -148,6 +161,20 @@ public class ColumnRenderBuffer extends RenderBuffer {
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");
@@ -173,6 +200,10 @@ public class ColumnRenderBuffer extends RenderBuffer {
if (b == null) continue;
b.destroy(false);
}
for (GLVertexBuffer b : vbosTransparent) {
if (b == null) continue;
b.destroy(false);
}
});
}
@@ -186,32 +217,23 @@ public class ColumnRenderBuffer extends RenderBuffer {
return getCurrentJobsCount() > MAX_CONCURRENT_CALL;
}
public static CompletableFuture<ColumnRenderBuffer[]> build(IClientLevel clientLevel, Reference<ColumnRenderBuffer> usedBufferSlotOpaque, Reference<ColumnRenderBuffer> usedBufferSlotTransparent, ColumnRenderSource data, ColumnRenderSource[] adjData) {
public static CompletableFuture<ColumnRenderBuffer> build(IClientLevel clientLevel, Reference<ColumnRenderBuffer> usedBufferSlot, ColumnRenderSource data, ColumnRenderSource[] adjData) {
if (isBusy()) return null;
//LOGGER.info("RenderRegion startBuild @ {}", data.sectionPos);
return CompletableFuture.supplyAsync(() -> {
try {
boolean enableTransparency = Config.Client.Graphics.Quality.transparency.get().tranparencyEnabled;
EVENT_LOGGER.trace("RenderRegion start QuadBuild @ {}", data.sectionPos);
boolean enableSkyLightCulling = Config.Client.Graphics.AdvancedGraphics.enableCaveCulling.get();
int skyLightCullingBelow = Config.Client.Graphics.AdvancedGraphics.caveCullingHeight.get();
// FIXME: Clamp also to the max world height.
skyLightCullingBelow = Math.max(skyLightCullingBelow, clientLevel.getMinY());
LodQuadBuilder builderOpaque = new LodQuadBuilder(enableSkyLightCulling,
(short) (skyLightCullingBelow - clientLevel.getMinY()));
LodQuadBuilder builder = new LodQuadBuilder(enableSkyLightCulling,
(short) (skyLightCullingBelow - clientLevel.getMinY()), enableTransparency);
LodQuadBuilder builderTransparent = new LodQuadBuilder(enableSkyLightCulling,
(short) (skyLightCullingBelow - clientLevel.getMinY()));
makeLodRenderData(builderOpaque, builderTransparent, data, adjData);
if (builderOpaque.getCurrentQuadsCount() > 0) {
//LOGGER.info("her");
}
makeLodRenderData(builder, data, adjData);
EVENT_LOGGER.trace("RenderRegion end QuadBuild @ {}", data.sectionPos);
LodQuadBuilder[] builders = new LodQuadBuilder[2];
builders[0] = builderOpaque;
builders[1] = builderTransparent;
return builders;
return builder;
} catch (UncheckedInterruptedException e) {
throw e;
}
@@ -220,35 +242,25 @@ public class ColumnRenderBuffer extends RenderBuffer {
throw e3;
}
}, BUFFER_BUILDERS)
.thenApplyAsync((builders) -> {
.thenApplyAsync((builder) -> {
try {
EVENT_LOGGER.trace("RenderRegion start Upload @ {}", data.sectionPos);
GLProxy glProxy = GLProxy.getInstance();
EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
EGLProxyContext oldContext = glProxy.getGlContext();
glProxy.setGlContext(EGLProxyContext.LOD_BUILDER);
ColumnRenderBuffer buffersSlotOpaque = usedBufferSlotOpaque.swap(null);
ColumnRenderBuffer buffersSlotTransparent = usedBufferSlotTransparent.swap(null);
ColumnRenderBuffer buffer = usedBufferSlot.swap(null);
if (buffersSlotOpaque == null)
buffersSlotOpaque = new ColumnRenderBuffer(
if (buffer == null)
buffer = new ColumnRenderBuffer(
new DhBlockPos(data.sectionPos.getCorner().getCorner(), clientLevel.getMinY())
);
if (buffersSlotTransparent == null)
buffersSlotTransparent = new ColumnRenderBuffer(
new DhBlockPos(data.sectionPos.getCorner().getCorner(), clientLevel.getMinY())
);
try {
buffersSlotOpaque.uploadBuffer(builders[0], method);
buffersSlotTransparent.uploadBuffer(builders[1], method);
buffer.uploadBuffer(builder, method);
EVENT_LOGGER.trace("RenderRegion end Upload @ {}", data.sectionPos);
ColumnRenderBuffer[] buffers = new ColumnRenderBuffer[2];
buffers[0] = buffersSlotOpaque;
buffers[1] = buffersSlotTransparent;
return buffers;
return buffer;
} catch (Exception e) {
buffersSlotOpaque.close();
buffersSlotTransparent.close();
buffer.close();
throw e;
} finally {
glProxy.setGlContext(oldContext);
@@ -262,14 +274,10 @@ public class ColumnRenderBuffer extends RenderBuffer {
}, BUFFER_UPLOADER).handle((v, e) -> {
//LOGGER.info("RenderRegion endBuild @ {}", data.sectionPos);
if (e != null) {
ColumnRenderBuffer buffersSlot;
if (!usedBufferSlotOpaque.isEmpty()) {
buffersSlot = usedBufferSlotOpaque.swap(null);
buffersSlot.close();
}
if (!usedBufferSlotTransparent.isEmpty()) {
buffersSlot = usedBufferSlotTransparent.swap(null);
buffersSlot.close();
ColumnRenderBuffer buffer;
if (!usedBufferSlot.isEmpty()) {
buffer = usedBufferSlot.swap(null);
buffer.close();
}
return null;
} else {
@@ -280,7 +288,7 @@ public class ColumnRenderBuffer extends RenderBuffer {
private static void makeLodRenderData(LodQuadBuilder quadBuilderOpaque, LodQuadBuilder quadBuilderTransparent, ColumnRenderSource region, ColumnRenderSource[] adjRegions) {
private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource region, ColumnRenderSource[] adjRegions) {
// Variable initialization
EDebugMode debugMode = Config.Client.Advanced.Debugging.debugMode.get();
@@ -372,23 +380,11 @@ public class ColumnRenderBuffer extends RenderBuffer {
long adjDataTop = i - 1 >= 0 ? posData.get(i - 1) : ColumnFormat.EMPTY_DATA;
long adjDataBot = i + 1 < posData.size() ? posData.get(i + 1) : ColumnFormat.EMPTY_DATA;
// We send the call to create the vertices
if(ColumnFormat.getAlpha(data) == 255 || !LodRenderer.transparencyEnabled)
{
CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, detailLevel,
x, z, quadBuilderOpaque, debugMode);
}
else
{
CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, detailLevel,
x, z, quadBuilderTransparent, debugMode);
}
CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, detailLevel,
x, z, quadBuilder, debugMode);
}
}
}
quadBuilderOpaque.mergeQuads();
if(LodRenderer.transparencyEnabled)
quadBuilderTransparent.mergeQuads();
quadBuilder.mergeQuads();
}
}
@@ -24,6 +24,7 @@ import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Objects;
import com.seibel.lod.core.render.RenderBuffer;
import com.seibel.lod.core.enums.ELodDirection;
@@ -47,7 +48,11 @@ public class LodQuadBuilder
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public final boolean skipQuadsWithZeroSkylight;
public final short skyLightCullingBelow;
final ArrayList<BufferQuad>[] quads = (ArrayList<BufferQuad>[]) new ArrayList[6];
@SuppressWarnings("unchecked")
final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
@SuppressWarnings("unchecked")
final ArrayList<BufferQuad>[] transparentQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
final boolean doTransparency;
public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][]
{
@@ -100,18 +105,17 @@ public class LodQuadBuilder
private int premergeCount = 0;
public LodQuadBuilder(boolean enableSkylightCulling, short skyLightCullingBelow)
public LodQuadBuilder(boolean enableSkylightCulling, short skyLightCullingBelow, boolean doTransparency)
{
for (int i = 0; i < 6; i++)
quads[i] = new ArrayList<>();
this.doTransparency = doTransparency;
for (int i = 0; i < 6; i++) {
opaqueQuads[i] = new ArrayList<>();
if (doTransparency) transparentQuads[i] = new ArrayList<>();
}
this.skipQuadsWithZeroSkylight = enableSkylightCulling;
this.skyLightCullingBelow = skyLightCullingBelow;
}
public void addQuadAdj(ELodDirection dir, short x, short y, short z,
short widthEastWest, short widthNorthSouthOrUpDown,
int color, byte skylight, byte blocklight)
@@ -121,7 +125,8 @@ public class LodQuadBuilder
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
BufferQuad quad = new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, skylight, blocklight, dir);
ArrayList<BufferQuad> qs = quads[dir.ordinal()];
ArrayList<BufferQuad> qs = (doTransparency && ColorUtil.getAlpha(color) < 255)
? transparentQuads[dir.ordinal()] : opaqueQuads[dir.ordinal()];
if (!qs.isEmpty() &&
(qs.get(qs.size()-1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
|| qs.get(qs.size()-1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown))
@@ -138,7 +143,8 @@ public class LodQuadBuilder
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, skylight, blocklight, ELodDirection.UP);
ArrayList<BufferQuad> qs = quads[ELodDirection.UP.ordinal()];
ArrayList<BufferQuad> qs = (doTransparency && ColorUtil.getAlpha(color) < 255)
? transparentQuads[ELodDirection.UP.ordinal()] : opaqueQuads[ELodDirection.UP.ordinal()];
if (!qs.isEmpty() &&
(qs.get(qs.size()-1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
|| qs.get(qs.size()-1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown))
@@ -154,7 +160,8 @@ public class LodQuadBuilder
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, skylight, blocklight, ELodDirection.DOWN);
ArrayList<BufferQuad> qs = quads[ELodDirection.DOWN.ordinal()];
ArrayList<BufferQuad> qs = (doTransparency && ColorUtil.getAlpha(color) < 255)
? transparentQuads[ELodDirection.DOWN.ordinal()] : opaqueQuads[ELodDirection.DOWN.ordinal()];
if (!qs.isEmpty() &&
(qs.get(qs.size()-1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
|| qs.get(qs.size()-1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown))
@@ -249,35 +256,38 @@ public class LodQuadBuilder
public void mergeQuads()
{
long mergeCount = 0;
long preQuadsCount = getCurrentQuadsCount();
long preQuadsCount = getCurrentOpaqueQuadsCount() + getCurrentTransparentQuadsCount();
if (preQuadsCount <= 1)
return;
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
mergeCount += mergeQuadsInternal(directionIndex, BufferMergeDirectionEnum.EastWest);
mergeCount += mergeQuadsInternal(opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
if (doTransparency)
mergeCount += mergeQuadsInternal(transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
// only run the second merge if the face is the top or bottom
//if (directionIndex == LodDirection.UP.ordinal() || directionIndex == LodDirection.DOWN.ordinal())
//{
// long pass2 = mergeQuadsInternal(directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
// mergeCount += pass2;
//}
if (directionIndex == ELodDirection.UP.ordinal() || directionIndex == ELodDirection.DOWN.ordinal())
{
mergeCount += mergeQuadsInternal(opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
if (doTransparency)
mergeCount += mergeQuadsInternal(transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
}
}
long postQuadsCount = getCurrentQuadsCount();
long postQuadsCount = getCurrentOpaqueQuadsCount() + getCurrentTransparentQuadsCount();
//if (mergeCount != 0)
LOGGER.debug("Merged {}/{}({}) quads", mergeCount, preQuadsCount, mergeCount / (double) preQuadsCount);
}
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
private long mergeQuadsInternal(int directionIndex, BufferMergeDirectionEnum mergeDirection)
private static long mergeQuadsInternal(ArrayList<BufferQuad>[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection)
{
if (quads[directionIndex].size() <= 1)
if (list[directionIndex].size() <= 1)
return 0;
quads[directionIndex].sort( (objOne, objTwo) -> objOne.compare(objTwo, mergeDirection) );
list[directionIndex].sort( (objOne, objTwo) -> objOne.compare(objTwo, mergeDirection) );
long mergeCount = 0;
ListIterator<BufferQuad> iter = quads[directionIndex].listIterator();
ListIterator<BufferQuad> iter = list[directionIndex].listIterator();
BufferQuad currentQuad = iter.next();
while (iter.hasNext())
{
@@ -295,13 +305,13 @@ public class LodQuadBuilder
currentQuad = nextQuad;
}
}
quads[directionIndex].removeIf(o -> o == null);
list[directionIndex].removeIf(Objects::isNull);
return mergeCount;
}
public Iterator<ByteBuffer> makeVertexBuffers()
public Iterator<ByteBuffer> makeOpaqueVertexBuffers()
{
return new Iterator<ByteBuffer>()
{
@@ -312,7 +322,7 @@ public class LodQuadBuilder
private int skipEmpty(int d)
{
while (d < 6 && quads[d].isEmpty())
while (d < 6 && opaqueQuads[d].isEmpty())
d++;
return d;
}
@@ -344,16 +354,82 @@ public class LodQuadBuilder
private void writeData()
{
int i = quad;
for (; i < quads[dir].size(); i++)
for (; i < opaqueQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, quads[dir].get(i));
putQuad(bb, opaqueQuads[dir].get(i));
}
if (i >= quads[dir].size())
if (i >= opaqueQuads[dir].size())
{
quad = 0;
dir++;
dir = skipEmpty(dir);
}
else
{
quad = i;
}
}
};
}
public Iterator<ByteBuffer> makeTransparentVertexBuffers()
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(RenderBuffer.FULL_SIZED_BUFFER)
.order(ByteOrder.nativeOrder());
int dir = skipEmpty(0);
int quad = 0;
private int skipEmpty(int d)
{
while (d < 6 && transparentQuads[d].isEmpty())
d++;
return d;
}
@Override
public boolean hasNext()
{
return dir < 6;
}
@Override
public ByteBuffer next()
{
if (dir >= 6)
{
return null;
}
bb.clear();
bb.limit(RenderBuffer.FULL_SIZED_BUFFER);
while (bb.hasRemaining() && dir < 6)
{
writeData();
}
bb.limit(bb.position());
bb.rewind();
return bb;
}
private void writeData()
{
int i = quad;
for (; i < transparentQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, transparentQuads[dir].get(i));
}
if (i >= transparentQuads[dir].size())
{
quad = 0;
dir++;
@@ -366,14 +442,13 @@ public class LodQuadBuilder
}
};
}
public interface BufferFiller
{
/** If true: more data needs to be filled */
boolean fill(GLVertexBuffer vbo);
}
public BufferFiller makeBufferFiller(EGpuUploadMethod method)
public BufferFiller makeOpaqueBufferFiller(EGpuUploadMethod method)
{
return new BufferFiller()
{
@@ -414,10 +489,10 @@ public class LodQuadBuilder
private int _countRemainingQuads()
{
int a = quads[dir].size() - quad;
for (int i = dir + 1; i < quads.length; i++)
int a = opaqueQuads[dir].size() - quad;
for (int i = dir + 1; i < opaqueQuads.length; i++)
{
a += quads[i].size();
a += opaqueQuads[i].size();
}
return a;
}
@@ -427,20 +502,20 @@ public class LodQuadBuilder
int startQ = quad;
int i = startQ;
for (i = startQ; i < quads[dir].size(); i++)
for (i = startQ; i < opaqueQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, quads[dir].get(i));
putQuad(bb, opaqueQuads[dir].get(i));
}
if (i >= quads[dir].size())
if (i >= opaqueQuads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && quads[dir].isEmpty())
while (dir < 6 && opaqueQuads[dir].isEmpty())
dir++;
}
else
@@ -450,21 +525,112 @@ public class LodQuadBuilder
}
};
}
public int getCurrentQuadsCount()
public BufferFiller makeTransparentBufferFiller(EGpuUploadMethod method)
{
return new BufferFiller()
{
int dir = 0;
int quad = 0;
public boolean fill(GLVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.setVertexCount(0);
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > RenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = RenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * RenderBuffer.QUADS_BYTE_SIZE, method,
RenderBuffer.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * RenderBuffer.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer();
vbo.setVertexCount(numOfQuads*4);
return dir < 6;
}
private int _countRemainingQuads()
{
int a = transparentQuads[dir].size() - quad;
for (int i = dir + 1; i < transparentQuads.length; i++)
{
a += transparentQuads[i].size();
}
return a;
}
private void writeData(ByteBuffer bb)
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < transparentQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, transparentQuads[dir].get(i));
}
if (i >= transparentQuads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && transparentQuads[dir].isEmpty())
dir++;
}
else
{
quad = i;
}
}
};
}
public int getCurrentOpaqueQuadsCount()
{
int i = 0;
for (ArrayList<BufferQuad> qs : quads)
for (ArrayList<BufferQuad> qs : opaqueQuads)
i += qs.size();
return i;
}
public int getCurrentTransparentQuadsCount()
{
if (!doTransparency) return 0;
int i = 0;
for (ArrayList<BufferQuad> qs : transparentQuads)
i += qs.size();
return i;
}
/** Returns how many Buffers will be needed to render everything in this builder. */
public int getCurrentNeededVertexBufferCount()
/** Returns how many Buffers will be needed to render opaque quads in this builder. */
public int getCurrentNeededOpaqueVertexBufferCount()
{
return MathUtil.ceilDiv(getCurrentQuadsCount(), RenderBuffer.MAX_QUADS_PER_BUFFER);
return MathUtil.ceilDiv(getCurrentOpaqueQuadsCount(), RenderBuffer.MAX_QUADS_PER_BUFFER);
}
/** Returns how many Buffers will be needed to render transparent quads in this builder. */
public int getCurrentNeededTransparentVertexBufferCount()
{
if (!doTransparency) return 0;
return MathUtil.ceilDiv(getCurrentTransparentQuadsCount(), RenderBuffer.MAX_QUADS_PER_BUFFER);
}
}
@@ -9,6 +9,7 @@ import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
// WARNING: This is not THREAD-SAFE!
public class IdBiomeBlockStateMap {
@@ -47,7 +48,7 @@ public class IdBiomeBlockStateMap {
final ArrayList<Entry> entries = new ArrayList<>();
final HashMap<Entry, Integer> idMap = new HashMap<>();
final ConcurrentHashMap<Entry, Integer> idMap = new ConcurrentHashMap<>(); // FIXME: Improve performance
public Entry get(int id) {
return entries.get(id);
@@ -46,9 +46,6 @@ public class BatchGenerator implements IChunkGenerator
public ILevel targetLodLevel;
public static final int generationGroupSize = 4;
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
// private int estimatedSampleNeeded = 128;
// private int estimatedPointsToQueue = 1;
public BatchGenerator(ILevel targetLodLevel) {
this.targetLodLevel = targetLodLevel;
@@ -56,198 +53,6 @@ public class BatchGenerator implements IChunkGenerator
LOGGER.info("Batch Chunk Generator initialized");
}
// @SuppressWarnings("unused")
// public void queueGenerationRequests() {
// EDistanceGenerationMode mode = Config.Client.WorldGenerator.distanceGenerationMode.get();
// int newThreadCount = Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get()<1 ? 1 : (int) Math.ceil(Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get());
// if (newThreadCount != previousThreadCount) {
// generationGroup.resizeThreadPool(newThreadCount);
// previousThreadCount = newThreadCount;
// }
// if (estimatedPointsToQueue < newThreadCount)
// estimatedPointsToQueue = newThreadCount;
//
// EGenerationPriority priority = Config.Client.WorldGenerator.generationPriority.get();
// if (priority == EGenerationPriority.AUTO)
// priority = MC.hasSinglePlayerServer() ? EGenerationPriority.FAR_FIRST : EGenerationPriority.NEAR_FIRST;
//
// generationGroup.updateAllFutures();
// if (!MC.hasSinglePlayerServer())
// return;
// if (!LodUtil.checkRamUsage(0.1, 64)) return;
//
// int eventsCount = generationGroup.getEventCount();
// // If we still all jobs running, return.
// if (eventsCount >= estimatedPointsToQueue) {
// estimatedPointsToQueue--;
// if (estimatedPointsToQueue < newThreadCount)
// estimatedPointsToQueue = newThreadCount;
// return;
// }
//
// final int targetToGenerate = estimatedPointsToQueue - eventsCount;
// int toGenerate = targetToGenerate;
// int positionGoneThough = 0;
//
// // round the player's block position down to the nearest chunk BlockPos
// int playerPosX = MC.getPlayerBlockPos().getX();
// int playerPosZ = MC.getPlayerBlockPos().getZ();
// double runTimeRatio = Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get()>1 ? 1.0 : Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get();
//
// //FIXME
// PosToGenerateContainer posToGenerate = null;//lodDim.getPosToGenerate(estimatedSampleNeeded, playerPosX, playerPosZ,
// //priority, mode);
//
// if (eventsCount == 0 && posToGenerate.getNumberOfPos() >= estimatedSampleNeeded) {
// estimatedPointsToQueue++;
// if (estimatedPointsToQueue > newThreadCount * 10)
// estimatedPointsToQueue = newThreadCount * 10;
// }
//
// // ApiShared.LOGGER.info("PosToGenerate: {}", posToGenerate);
//
// // Find the max number of iterations we need to go though.
// // We are checking one FarPos, and one NearPos per iterations.
// // This ensures we aren't just always picking one or the other.
// Steps targetStep;
// switch (mode) {
// case NONE:
// targetStep = Steps.Empty; // NOTE: Only load in existing chunks. No new chunk generation
// break;
// case BIOME_ONLY:
// targetStep = Steps.Biomes; // NOTE: No block. Require fake height in LodBuilder
// break;
// case BIOME_ONLY_SIMULATE_HEIGHT:
// targetStep = Steps.Noise; // NOTE: Stone only. Require fake surface
// break;
// case SURFACE:
// targetStep = Steps.Surface; // Carvers or Surface???
// break;
// case FEATURES:
// case FULL:
// targetStep = Steps.Features;
// break;
// default:
// assert false;
// return;
// }
//
// if (ENABLE_GENERATOR_STATS_LOGGING)
// LOGGER.info("WorldGen. Near:" + posToGenerate.getNumberOfNearPos() + " Far:"
// + posToGenerate.getNumberOfFarPos());
// if (priority == EGenerationPriority.FAR_FIRST || priority == EGenerationPriority.BALANCED) {
//
// int nearCount = posToGenerate.getNumberOfNearPos();
// int farCount = posToGenerate.getNumberOfFarPos();
// if (ENABLE_GENERATOR_STATS_LOGGING)
// LOGGER.info("WorldGen. Near:" + nearCount + " Far:" + farCount);
// int maxIteration = Math.max(nearCount, farCount);
// for (int i = 0; i < maxIteration; i++) {
//
// // We have farPos to go though
// if (i < farCount && posToGenerate.getNthDetail(i, false) != 0) {
// positionGoneThough++;
// byte detailLevel = (byte) (posToGenerate.getNthDetail(i, false) - 1);
// int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, false));
// int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, false));
// int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
// if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, false, runTimeRatio)) {
// toGenerate--;
// }
// }
// if (toGenerate <= 0)
// break;
//
// // We have nearPos to go though
// if (i < nearCount && posToGenerate.getNthDetail(i, true) != 0) {
// positionGoneThough++;
// byte detailLevel = (byte) (posToGenerate.getNthDetail(i, true) - 1);
// int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, true));
// int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, true));
// int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
// if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, true, runTimeRatio)) {
// toGenerate--;
// }
// }
//
// if (toGenerate <= 0)
// break;
// }
// } else {
// int nearCount = posToGenerate.getNumberOfNearPos();
// for (int i = 0; i < nearCount; i++) {
//
// // We have nearPos to go though
// if (posToGenerate.getNthDetail(i, true) != 0) {
// positionGoneThough++;
// byte detailLevel = (byte) (posToGenerate.getNthDetail(i, true) - 1);
// int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, true));
// int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, true));
// int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
// if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, true, runTimeRatio)) {
// toGenerate--;
// }
// if (toGenerate <= 0)
// break;
// }
// }
// // Only do far gen if toGenerate is non 0 and that we have requested all samples
// // we can get.
// if (toGenerate > 0 && estimatedSampleNeeded > posToGenerate.getNumberOfPos()) {
// int farCount = posToGenerate.getNumberOfFarPos();
// for (int i = 0; i < farCount; i++) {
// // We have farPos to go though
// if (posToGenerate.getNthDetail(i, false) != 0) {
// positionGoneThough++;
// byte detailLevel = (byte) (posToGenerate.getNthDetail(i, false) - 1);
// int chunkX = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosX(i, false));
// int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posToGenerate.getNthPosZ(i, false));
// int genSize = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? 0 : generationGroupSize;
// if (generationGroup.tryAddPoint(chunkX, chunkZ, genSize, targetStep, false, runTimeRatio)) {
// toGenerate--;
// }
// }
// if (toGenerate <= 0)
// break;
// }
// }
// }
//
// if (targetToGenerate != toGenerate && ENABLE_GENERATOR_STATS_LOGGING) {
// if (toGenerate <= 0) {
// LOGGER.info(
// "WorldGenerator: Sampled " + posToGenerate.getNumberOfPos() + " out of " + estimatedSampleNeeded
// + " points, started all targeted " + targetToGenerate + " generations.");
// } else {
// LOGGER.info("WorldGenerator: Sampled " + posToGenerate.getNumberOfPos() + " out of "
// + estimatedSampleNeeded + " points, started " + (targetToGenerate - toGenerate)
// + " out of targeted " + targetToGenerate + " generations.");
// }
// }
//
// if (toGenerate > 0 && estimatedSampleNeeded <= posToGenerate.getNumberOfPos()) {
// // We failed to generate enough points from the samples.
// // Let's increase the estimatedSampleNeeded.
// estimatedSampleNeeded *= 1.3;
// // Ensure wee don't go to basically infinity
// if (estimatedSampleNeeded > 32768)
// estimatedSampleNeeded = 32768;
// if (ENABLE_GENERATOR_STATS_LOGGING)
// LOGGER.info("WorldGenerator: Increasing estimatedSampleNeeded to " + estimatedSampleNeeded);
//
// } else if (toGenerate <= 0 && positionGoneThough * 1.5 < posToGenerate.getNumberOfPos()) {
// // We haven't gone through half of them, and it's already enough.
// // Let's shrink the estimatedSampleNeeded.
// estimatedSampleNeeded /= 1.2;
// // Ensure we don't go near zero.
// if (estimatedSampleNeeded < 4)
// estimatedSampleNeeded = 4;
// if (ENABLE_GENERATOR_STATS_LOGGING)
// LOGGER.info("WorldGenerator: Decreasing estimatedSampleNeeded to " + estimatedSampleNeeded);
// }
//
// }
public void stop(boolean blocking) {
LOGGER.info("1.18 Experimental Chunk Generator shutting down...");
generationGroup.stop(blocking);
@@ -82,7 +82,7 @@ public class LodRenderSection {
}
public boolean canRender() {
return isLoaded() && isRenderEnabled && lodRenderSource != null && lodRenderSource.isRenderReady();
return isLoaded() && isRenderEnabled && lodRenderSource != null;
}
public boolean isLoaded() {
@@ -32,7 +32,8 @@ public abstract class RenderBuffer implements AutoCloseable
// ========== Called by render thread ==========
/* Called on... well... rendering.
* Return false if nothing rendered. (Optional) */
public abstract boolean render(LodRenderer renderContext);
public abstract boolean renderOpaque(LodRenderer renderContext);
public abstract boolean renderTransparent(LodRenderer renderContext);
// ========== Called by any thread. (thread safe) ==========
@@ -57,5 +58,4 @@ public abstract class RenderBuffer implements AutoCloseable
public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
}
@@ -1,63 +1,128 @@
package com.seibel.lod.core.render;
import com.seibel.lod.core.datatype.LodRenderSource;
import com.seibel.lod.core.enums.ELodDirection;
import com.seibel.lod.core.pos.Pos2D;
import com.seibel.lod.core.pos.DhSectionPos;
import com.seibel.lod.core.render.renderer.LodRenderer;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.gridList.MovableGridRingList;
import com.seibel.lod.core.util.math.Mat4f;
import com.seibel.lod.core.util.math.Vec3f;
import com.seibel.lod.core.util.objects.SortedArraySet;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicReference;
public class RenderBufferHandler {
public final LodQuadTree target;
private final MovableGridRingList<RenderBufferNode> renderBufferNodes;
private static class LoadedRenderBuffer {
public final RenderBuffer buffer;
public final DhSectionPos pos;
LoadedRenderBuffer(RenderBuffer buffer, DhSectionPos pos) {
this.buffer = buffer;
this.pos = pos;
}
}
// TODO: Make sorting go into the update loop instead of the render loop as it doesn't need to be done every frame
private SortedArraySet<LoadedRenderBuffer> loadedNearToFarBuffers = null;
// The followiing buildRenderList sorting method is based on the following reddit post:
// https://www.reddit.com/r/VoxelGameDev/comments/a0l8zc/correct_depthordering_for_translucent_discrete/
public void buildRenderList(Vec3f lookForwardVector) {
ELodDirection[] axisDirections = new ELodDirection[3];
// Do the axis that are longest first (i.e. the largest absolute value of the lookForwardVector)
// , with the sign being the opposite of the respective lookForwardVector component's sign
float absX = Math.abs(lookForwardVector.x);
float absY = Math.abs(lookForwardVector.y);
float absZ = Math.abs(lookForwardVector.z);
ELodDirection xDir = lookForwardVector.x < 0 ? ELodDirection.EAST : ELodDirection.WEST;
ELodDirection yDir = lookForwardVector.y < 0 ? ELodDirection.UP : ELodDirection.DOWN;
ELodDirection zDir = lookForwardVector.z < 0 ? ELodDirection.SOUTH : ELodDirection.NORTH;
if (absX >= absY && absX >= absZ) {
axisDirections[0] = xDir;
if (absY >= absZ) {
axisDirections[1] = yDir;
axisDirections[2] = zDir;
} else {
axisDirections[1] = zDir;
axisDirections[2] = yDir;
}
} else if (absY >= absX && absY >= absZ) {
axisDirections[0] = yDir;
if (absX >= absZ) {
axisDirections[1] = xDir;
axisDirections[2] = zDir;
} else {
axisDirections[1] = zDir;
axisDirections[2] = xDir;
}
} else {
axisDirections[0] = zDir;
if (absX >= absY) {
axisDirections[1] = xDir;
axisDirections[2] = yDir;
} else {
axisDirections[1] = yDir;
axisDirections[2] = xDir;
}
}
// Now that we have the axis directions, we can sort the render list
Comparator<LoadedRenderBuffer> sortFarToNear = (a, b) -> {
Pos2D aPos = a.pos.getCenter().getCenter().toPos2D();
Pos2D bPos = b.pos.getCenter().getCenter().toPos2D();
for (ELodDirection axisDirection : axisDirections) {
if (axisDirection.getAxis().isVertical()) continue; // We works on the horizontal plane only for section sorting
int abDiff;
if (axisDirection.getAxis().equals(ELodDirection.Axis.X)) {
abDiff = aPos.x - bPos.x;
} else {
abDiff = aPos.y - bPos.y;
}
if (abDiff == 0) continue;
if (axisDirection.getAxisDirection().equals(ELodDirection.AxisDirection.NEGATIVE)) {
abDiff = -abDiff; // Reverse the sign
}
return abDiff;
}
return a.pos.sectionDetail - b.pos.sectionDetail; // If all else fails, sort by detail
};
Comparator<LoadedRenderBuffer> sortNearToFar = (a, b) -> -sortFarToNear.compare(a, b);
// Build the sorted list
loadedNearToFarBuffers = new SortedArraySet<>(sortNearToFar);
// Add all the loaded buffers to the sorted list
renderBufferNodes.forEach((r) -> {if (r!=null) r.collect(loadedNearToFarBuffers);});
}
class RenderBufferNode implements AutoCloseable {
public final DhSectionPos pos;
public volatile RenderBufferNode[] children = null;
//FIXME: The multiple Atomics will cause race conditions between them!
public final AtomicReference<RenderBuffer> renderBufferSlotOpaque = new AtomicReference<>();
public final AtomicReference<RenderBuffer> renderBufferSlotTransparent = new AtomicReference<>();
public final AtomicReference<RenderBuffer> renderBufferSlot = new AtomicReference<>();
public RenderBufferNode(DhSectionPos pos) {
this.pos = pos;
}
/**
* This will render all opaque lods
* @param renderContext
*/
public void renderOpaque(LodRenderer renderContext) {
public void collect(SortedArraySet<LoadedRenderBuffer> sortedSet) {
RenderBuffer buff;
buff = renderBufferSlotOpaque.get();
buff = renderBufferSlot.get();
if (buff != null) {
buff.render(renderContext);
sortedSet.add(new LoadedRenderBuffer(buff, pos));
} else {
RenderBufferNode[] childs = children;
if (childs != null) {
for (RenderBufferNode child : childs) {
child.renderOpaque(renderContext);
}
}
}
}
/**
* This will render all transparent lods
* @param renderContext
*/
public void renderTransparent(LodRenderer renderContext) {
RenderBuffer buff;
buff = renderBufferSlotTransparent.get();
if (buff != null) {
buff.render(renderContext);
} else {
RenderBufferNode[] childs = children;
if (childs != null) {
for (RenderBufferNode child : childs) {
child.renderTransparent(renderContext);
child.collect(sortedSet);
}
}
}
@@ -77,17 +142,13 @@ public class RenderBufferHandler {
boolean shouldRender = section.canRender();
if (!shouldRender) {
//TODO: Does this really need to force the old buffer to not be rendered?
RenderBuffer buff = renderBufferSlotOpaque.getAndSet(null);
if (buff != null) {
buff.close();
}
buff = renderBufferSlotTransparent.getAndSet(null);
RenderBuffer buff = renderBufferSlot.getAndSet(null);
if (buff != null) {
buff.close();
}
} else {
LodUtil.assertTrue(container != null); // section.isLoaded() should have ensured this
container.trySwapRenderBuffer(target, renderBufferSlotOpaque, renderBufferSlotTransparent);
container.trySwapRenderBuffer(target, renderBufferSlot);
}
// Update children's render buffer state
@@ -109,7 +170,7 @@ public class RenderBufferHandler {
} else {
if (children != null) {
//FIXME: Concurrency issue here: If render thread is concurrently using the child's buffer,
// and this thread got priority to close the buffer, it causes a bug wher the render thread
// and this thread got priority to close the buffer, it causes a bug where the render thread
// will be using a closed buffer!!!!
RenderBufferNode[] childs = children;
children = null;
@@ -128,11 +189,7 @@ public class RenderBufferHandler {
}
}
RenderBuffer buff;
buff = renderBufferSlotOpaque.getAndSet(null);
if (buff != null) {
buff.close();
}
buff = renderBufferSlotTransparent.getAndSet(null);
buff = renderBufferSlot.getAndSet(null);
if (buff != null) {
buff.close();
}
@@ -146,14 +203,19 @@ public class RenderBufferHandler {
renderBufferNodes = new MovableGridRingList<>(referenceList.getHalfSize(), center);
}
public void render(LodRenderer renderContext) {
//TODO: This might get locked by update() causing move() call. Is there a way to avoid this?
// Maybe dupe the base list and use atomic swap on render? Or is this not worth it?
//TODO: This might get locked by update() causing move() call. Is there a way to avoid this?
// Maybe dupe the base list and use atomic swap on render? Or is this not worth it?
public void prepare(LodRenderer renderContext) {
buildRenderList(renderContext.getLookVector());
}
public void renderOpaque(LodRenderer renderContext) {
//TODO: Directional culling
//TODO: Ordered by distance
renderBufferNodes.forEachOrdered(n -> n.renderOpaque(renderContext));
loadedNearToFarBuffers.forEach(b -> b.buffer.renderOpaque(renderContext));
}
public void renderTransparent(LodRenderer renderContext) {
if(LodRenderer.transparencyEnabled)
renderBufferNodes.forEachOrdered(n -> n.renderTransparent(renderContext));
loadedNearToFarBuffers.forEach(b -> b.buffer.renderTransparent(renderContext));
}
public void update() {
@@ -186,31 +248,6 @@ public class RenderBufferHandler {
// Update node
node.update();
});
/**TODO improve the ordering*/
/* DHBlockPos playerPos = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).getPlayerBlockPos();
int x = playerPos.x;
int z = playerPos.z;
Comparator<RenderBufferNode> byDistance = new Comparator<RenderBufferNode>() {
@Override
public int compare(RenderBufferNode o1, RenderBufferNode o2) {
if ((o1 == null) && (o2 == null)) {
return 0;
} else if (o1 == null) {
return 1;
} else if (o2 == null) {
return -1;
}
int x1 = o1.pos.sectionX;
int z1 = o1.pos.sectionZ;
int x2 = o2.pos.sectionX;
int z2 = o2.pos.sectionZ;
return Integer.compare((x1 - x) ^ 2 + (z1 - z) ^ 2, (x2 - x) ^ 2 + (z2 - z) ^ 2);
}
};
renderBufferNodes.sort(byDistance);*/
}
public void close() {
@@ -36,6 +36,7 @@ public class GLState {
public int blendSrc;
public int blendDst;
public boolean depth;
public boolean depthWrite;
public int depthFunc;
public boolean stencil;
public int stencilFunc;
@@ -61,6 +62,7 @@ public class GLState {
blendSrc = GL32.glGetInteger(GL32.GL_BLEND_SRC);
blendDst = GL32.glGetInteger(GL32.GL_BLEND_DST);
depth = GL32.glIsEnabled(GL32.GL_DEPTH_TEST);
depthWrite = GL32.glGetInteger(GL32.GL_DEPTH_WRITEMASK) == GL32.GL_TRUE;
depthFunc = GL32.glGetInteger(GL32.GL_DEPTH_FUNC);
stencil = GL32.glIsEnabled(GL32.GL_STENCIL_TEST);
stencilFunc = GL32.glGetInteger(GL32.GL_STENCIL_FUNC);
@@ -100,7 +102,9 @@ public class GLState {
GL32.glBindVertexArray(vao);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo);
GL32.glBindBuffer(GL32.GL_ELEMENT_ARRAY_BUFFER, ebo);
GL32.glUseProgram(prog);
GL32.glDepthMask(depthWrite);
GL32.glBlendFunc(blendSrc, blendDst);
if (depth) {
GL32.glEnable(GL32.GL_DEPTH_TEST);
@@ -115,7 +119,6 @@ public class GLState {
}
GL32.glStencilFunc(stencilFunc, stencilRef, stencilMask);
GL32.glViewport(view[0], view[1], view[2], view[3]);
GL32.glUseProgram(prog);
if (cull) {
GL32.glEnable(GL32.GL_CULL_FACE);
} else {
@@ -85,6 +85,10 @@ public class LodRenderer
GL32.glDrawElements(GL32.GL_TRIANGLES, (vbo.getVertexCount()/4)*6,
quadIBO.getType(), 0);
}
public Vec3f getLookVector() {
return MC_RENDER.getLookAtVector();
}
public static class LagSpikeCatcher {
long timer = System.nanoTime();
@@ -130,7 +134,6 @@ public class LodRenderer
EVENT_LOGGER.error("drawLODs() called after close()!");
return;
}
// get MC's shader program
// Save all MC render state
@@ -179,13 +182,9 @@ public class LodRenderer
transparencyEnabled = Config.Client.Graphics.Quality.transparency.get().tranparencyEnabled;
fakeOceanFloor = Config.Client.Graphics.Quality.transparency.get().fakeTransparencyEnabled;
if(transparencyEnabled) {
GL32.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA);
GL32.glEnable(GL32.GL_BLEND);
}else{
GL32.glDisable(GL32.GL_BLEND);
}
GL32.glDisable(GL32.GL_BLEND); // We render opaque first, then transparent
GL32.glDepthMask(true);
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
/*---------Bind required objects--------*/
@@ -223,6 +222,8 @@ public class LodRenderer
//GL32.glEnable( GL32.GL_POLYGON_OFFSET_FILL );
//GL32.glPolygonOffset( 1f, 1f );
bufferHandler.prepare(this);
//===========//
// rendering //
//===========//
@@ -233,11 +234,20 @@ public class LodRenderer
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
DhBlockPos cameraBlockPos = MC_RENDER.getCameraBlockPosition();
Vec3f cameraDir = MC_RENDER.getLookAtVector();
int drawCount = 0;
//TODO: Directional culling
bufferHandler.render(this);
bufferHandler.renderOpaque(this);
//======================//
// render transparency //
//======================//
if (LodRenderer.transparencyEnabled) {
GL32.glEnable(GL32.GL_BLEND);
GL32.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA);
GL32.glDepthMask(false); // This so that even on incorrect sorting of transparent blocks, it still mostly looks correct
bufferHandler.renderTransparent(this);
GL32.glDepthMask(true); // Apparently the depth mask state is stored in the FBO, so glState fails to restore it...
}
//if (drawCall==0)
// tickLogger.info("DrawCall Count: {}", drawCount);
@@ -0,0 +1,155 @@
package com.seibel.lod.core.util.objects;
import java.util.*;
public class SortedArraySet<E> implements SortedSet<E> {
private final ArrayList<E> list;
private final Comparator<? super E> comparator;
public SortedArraySet() {
list = new ArrayList<>();
comparator = null;
}
public SortedArraySet(Comparator<? super E> comparator) {
list = new ArrayList<>();
this.comparator = comparator;
}
public SortedArraySet(Collection<? extends E> collection) {
list = new ArrayList<>(collection);
comparator = null;
list.sort(null);
}
public SortedArraySet(Collection<? extends E> collection, Comparator<? super E> comparator) {
list = new ArrayList<>(collection);
this.comparator = comparator;
list.sort(comparator);
}
@Override
public Comparator<? super E> comparator() {
return comparator;
}
@Override
public E first() {
return list.get(0);
}
@Override
public E last() {
return list.get(list.size() - 1);
}
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public boolean contains(Object o) {
return list.contains(o);
}
@Override
public Iterator<E> iterator() {
return list.iterator();
}
public ListIterator<E> listIterator() {
return list.listIterator();
}
@Override
public Object[] toArray() {
return list.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
@Override
public boolean add(E e) {
int index = Collections.binarySearch(list, e, comparator);
if (index < 0) {
index = ~index;
}
list.add(index, e);
return true;
}
@Override
public boolean remove(Object o) {
return list.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
boolean changed = false;
for (E e : c) {
changed |= add(e);
}
return changed;
}
@Override
public boolean retainAll(Collection<?> c) {
return list.retainAll(c);
}
@Override
public boolean removeAll(Collection<?> c) {
return list.removeAll(c);
}
@Override
public void clear() {
list.clear();
}
@Override
public String toString() {
return "SortedArraySet{" +
"list=" + list +
", comparator=" + comparator +
'}';
}
@Override
public SortedSet<E> subSet(E fromElement, E toElement) {
int fromIndex = Collections.binarySearch(list, fromElement, comparator);
if (fromIndex < 0) fromIndex = ~fromIndex;
int toIndex = Collections.binarySearch(list, toElement, comparator);
if (toIndex < 0) toIndex = ~toIndex;
return new SortedArraySet<>(list.subList(fromIndex, toIndex), comparator);
}
@Override
public SortedSet<E> headSet(E toElement) {
int toIndex = Collections.binarySearch(list, toElement, comparator);
if (toIndex < 0) toIndex = ~toIndex;
return new SortedArraySet<>(list.subList(0, toIndex), comparator);
}
@Override
public SortedSet<E> tailSet(E fromElement) {
int fromIndex = Collections.binarySearch(list, fromElement, comparator);
if (fromIndex < 0) fromIndex = ~fromIndex;
return new SortedArraySet<>(list.subList(fromIndex, list.size()), comparator);
}
}