diff --git a/src/main/java/com/seibel/lod/core/builders/lodBuilding/bufferBuilding/CubicLodTemplate.java b/src/main/java/com/seibel/lod/core/builders/lodBuilding/bufferBuilding/CubicLodTemplate.java
index 8c68b69b6..1c19ad0ef 100644
--- a/src/main/java/com/seibel/lod/core/builders/lodBuilding/bufferBuilding/CubicLodTemplate.java
+++ b/src/main/java/com/seibel/lod/core/builders/lodBuilding/bufferBuilding/CubicLodTemplate.java
@@ -22,6 +22,8 @@ package com.seibel.lod.core.builders.lodBuilding.bufferBuilding;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.LodDataView;
+import com.seibel.lod.core.objects.a7.datatype.column.ColumnArrayView;
+import com.seibel.lod.core.objects.a7.datatype.column.ColumnBox;
import com.seibel.lod.core.objects.opengl.LodBox;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
@@ -104,4 +106,70 @@ public class CubicLodTemplate
fullBright ? 15 : DataPointUtil.getLightBlock(data), // setBlockLights
topData, botData, adjData, adjFillBlack); // setAdjData
}
+
+ public static void addLodToBuffer(long data, long topData, long botData, ColumnArrayView[][] adjData, byte detailLevel, int offsetPosX, int offsetOosZ, LodQuadBuilder quadBuilder, DebugMode debugging)
+ {
+ short width = (short) (1 << detailLevel);
+ short x = (short) LevelPosUtil.convert(detailLevel, offsetPosX, LodUtil.BLOCK_DETAIL_LEVEL);
+ short y = DataPointUtil.getDepth(data);
+ short z = (short) LevelPosUtil.convert(detailLevel, offsetOosZ, LodUtil.BLOCK_DETAIL_LEVEL);
+ short dy = (short) (DataPointUtil.getHeight(data) - y);
+ if (dy == 0)
+ return;
+ if (dy < 0)
+ {
+ throw new IllegalArgumentException("Negative y size for the data! Data: " + DataPointUtil.toString(data));
+ }
+
+ int color;
+ boolean fullBright = false;
+ switch (debugging) {
+ case OFF:
+ case SHOW_WIREFRAME:
+ {
+ float saturationMultiplier = (float)CONFIG.client().graphics().advancedGraphics().getSaturationMultiplier();
+ float brightnessMultiplier = (float)CONFIG.client().graphics().advancedGraphics().getBrightnessMultiplier();
+ if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0) {
+ color = DataPointUtil.getColor(data);
+ } else {
+ float[] ahsv = ColorUtil.argbToAhsv(DataPointUtil.getColor(data));
+ color = ColorUtil.ahsvToArgb(ahsv[0], ahsv[1], ahsv[2] * saturationMultiplier, ahsv[3] * brightnessMultiplier);
+ //ApiShared.LOGGER.info("Raw color:[{}], AHSV:{}, Out color:[{}]",
+ // ColorUtil.toString(DataPointUtil.getColor(data)),
+ // ahsv, ColorUtil.toString(color));
+ }
+ break;
+ }
+ case SHOW_DETAIL:
+ case SHOW_DETAIL_WIREFRAME:
+ {
+ color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel];
+ fullBright = true;
+ break;
+ }
+ case SHOW_GENMODE:
+ case SHOW_GENMODE_WIREFRAME:
+ {
+ color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[DataPointUtil.getGenerationMode(data)];
+ fullBright = true;
+ break;
+ }
+ case SHOW_OVERLAPPING_QUADS:
+ case SHOW_OVERLAPPING_QUADS_WIREFRAME:
+ {
+ color = ColorUtil.WHITE;
+ fullBright = true;
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown debug mode: " + debugging);
+ }
+ ColumnBox.addBoxQuadsToBuilder(quadBuilder, // buffer
+ width, dy, width, // setWidth
+ x, y, z, // setOffset
+ color, // setColor
+ DataPointUtil.getLightSky(data), // setSkyLights
+ fullBright ? 15 : DataPointUtil.getLightBlock(data), // setBlockLights
+ topData, botData, adjData); // setAdjData
+ }
}
diff --git a/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java b/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java
index 016526c30..caa1e213d 100644
--- a/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java
+++ b/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java
@@ -467,9 +467,9 @@ public abstract class LodQuadTree {
if (!section.isLoaded() && !section.isLoading()) {
section.load(getRenderDataProvider(), containerType);
}
- if (section.childCount == 4) section.enableRender();
+ if (section.childCount == 4) section.enableRender(this);
if (section.childCount == 0) section.disableRender();
- section.tick();
+ section.tick(this);
}
});
}
diff --git a/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java b/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java
index 4433b41b4..fafd8e83c 100644
--- a/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java
+++ b/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java
@@ -26,10 +26,10 @@ public class LodSection {
this.pos = pos;
}
- public void enableRender() {
+ public void enableRender(LodQuadTree quadTree) {
if (isRenderEnabled) return;
if (renderDataSource != null) {
- renderDataSource.enableRender();
+ renderDataSource.enableRender(quadTree);
}
isRenderEnabled = true;
}
@@ -46,12 +46,12 @@ public class LodSection {
loadFuture = renderDataProvider.createRenderData(renderDataSourceClass, pos);
}
- public void tick() {
+ public void tick(LodQuadTree quadTree) {
if (loadFuture != null && loadFuture.isDone()) {
renderDataSource = loadFuture.join();
loadFuture = null;
if (isRenderEnabled) {
- renderDataSource.enableRender();
+ renderDataSource.enableRender(quadTree);
}
}
}
diff --git a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnBox.java b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnBox.java
new file mode 100644
index 000000000..3df488088
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnBox.java
@@ -0,0 +1,309 @@
+/*
+ * This file is part of the Distant Horizons mod (formerly the LOD Mod),
+ * licensed under the GNU LGPL 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 Lesser 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.seibel.lod.core.objects.a7.datatype.column;
+
+import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
+import com.seibel.lod.core.enums.LodDirection;
+import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
+import com.seibel.lod.core.objects.LodDataView;
+import com.seibel.lod.core.util.ColorUtil;
+import com.seibel.lod.core.util.DataPointUtil;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
+
+public class ColumnBox
+{
+ private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
+
+ public static void addBoxQuadsToBuilder(LodQuadBuilder builder, short xSize, short ySize, short zSize, short x,
+ short y, short z, int color, byte skyLight, byte blockLight, long topData, long botData, ColumnArrayView[][] adjData)
+ {
+ short maxX = (short) (x + xSize);
+ short maxY = (short) (y + ySize);
+ short maxZ = (short) (z + zSize);
+ byte skyLightTop = skyLight;
+ byte skyLightBot = DataPointUtil.doesItExist(botData) ? DataPointUtil.getLightSky(botData) : 0;
+
+ // Up direction case
+ boolean skipTop = DataPointUtil.doesItExist(topData) && DataPointUtil.getDepth(topData) == maxY;// &&
+ // DataPointUtil.getAlpha(singleAdjDataPoint)
+ // == 255;
+ boolean skipBot = DataPointUtil.doesItExist(botData) && DataPointUtil.getHeight(botData) == y;// &&
+ // DataPointUtil.getAlpha(singleAdjDataPoint)
+ // == 255;
+
+ if (!skipTop)
+ builder.addQuadUp(x, maxY, z, xSize, zSize, ColorUtil.applyShade(color, MC.getShade(LodDirection.UP)), skyLightTop, blockLight);
+ if (!skipBot)
+ builder.addQuadDown(x, y, z, xSize, zSize, ColorUtil.applyShade(color, MC.getShade(LodDirection.DOWN)), skyLightBot, blockLight);
+
+ //If the adj pos is at the same level we cull the faces normally, otherwise we divide the face in two and cull the two part separately
+
+ //NORTH face vertex creation
+ {
+ ColumnArrayView[] adjDataNorth = adjData[LodDirection.NORTH.ordinal() - 2];
+ int adjOverlapNorth = ColorUtil.TRANSPARENT;
+ if (adjDataNorth == null)
+ {
+ builder.addQuadAdj(LodDirection.NORTH, x, y, z, xSize, ySize, color, (byte) 15, blockLight);
+ }
+ else if (adjDataNorth.length == 1)
+ {
+ makeAdjQuads(builder, adjDataNorth[0], LodDirection.NORTH, x, y, z, xSize, ySize,
+ color, adjOverlapNorth, skyLightTop, blockLight);
+ }
+ else
+ {
+ makeAdjQuads(builder, adjDataNorth[0], LodDirection.NORTH, x, y, z, (short) (xSize / 2), ySize,
+ color, adjOverlapNorth, skyLightTop, blockLight);
+ makeAdjQuads(builder, adjDataNorth[1], LodDirection.NORTH, (short) (x + xSize / 2), y, z, (short) (xSize / 2), ySize,
+ color, adjOverlapNorth, skyLightTop, blockLight);
+ }
+ }
+
+ //SOUTH face vertex creation
+ {
+ ColumnArrayView[] adjDataSouth = adjData[LodDirection.SOUTH.ordinal() - 2];
+ int adjOverlapSouth = ColorUtil.TRANSPARENT;
+ if (adjDataSouth == null)
+ {
+ builder.addQuadAdj(LodDirection.SOUTH, x, y, maxZ, xSize, ySize, color, (byte) 15, blockLight);
+ }
+ else if (adjDataSouth.length == 1)
+ {
+ makeAdjQuads(builder, adjDataSouth[0], LodDirection.SOUTH, x, y, maxZ, xSize, ySize,
+ color, adjOverlapSouth, skyLightTop, blockLight);
+ }
+ else
+ {
+ makeAdjQuads(builder, adjDataSouth[0], LodDirection.SOUTH, x, y, maxZ, (short) (xSize / 2), ySize,
+ color, adjOverlapSouth, skyLightTop, blockLight);
+
+ makeAdjQuads(builder, adjDataSouth[1], LodDirection.SOUTH, (short) (x + xSize / 2), y, maxZ, (short) (xSize / 2), ySize,
+ color, adjOverlapSouth, skyLightTop, blockLight);
+ }
+ }
+
+ //WEST face vertex creation
+ {
+ ColumnArrayView[] adjDataWest = adjData[LodDirection.WEST.ordinal() - 2];
+ int adjOverlapWest = ColorUtil.TRANSPARENT;
+ if (adjDataWest == null)
+ {
+ builder.addQuadAdj(LodDirection.WEST, x, y, z, zSize, ySize, color, (byte) 15, blockLight);
+ }
+ else if (adjDataWest.length == 1)
+ {
+ makeAdjQuads(builder, adjDataWest[0], LodDirection.WEST, x, y, z, zSize, ySize,
+ color, adjOverlapWest, skyLightTop, blockLight);
+ }
+ else
+ {
+ makeAdjQuads(builder, adjDataWest[0], LodDirection.WEST, x, y, z, (short) (zSize / 2), ySize,
+ color, adjOverlapWest, skyLightTop, blockLight);
+ makeAdjQuads(builder, adjDataWest[1], LodDirection.WEST, x, y, (short) (z + zSize / 2), (short) (zSize / 2), ySize,
+ color, adjOverlapWest, skyLightTop, blockLight);
+ }
+ }
+
+ //EAST face vertex creation
+ {
+ ColumnArrayView[] adjDataEast = adjData[LodDirection.EAST.ordinal() - 2];
+ int adjOverlapEast = ColorUtil.TRANSPARENT;
+ if (adjData[LodDirection.EAST.ordinal() - 2] == null)
+ {
+ builder.addQuadAdj(LodDirection.EAST, maxX, y, z, zSize, ySize, color, (byte) 15, blockLight);
+ }
+ else if (adjDataEast.length == 1)
+ {
+ makeAdjQuads(builder, adjDataEast[0], LodDirection.EAST, maxX, y, z, zSize, ySize,
+ color, adjOverlapEast, skyLightTop, blockLight);
+ }
+ else
+ {
+ makeAdjQuads(builder, adjDataEast[0], LodDirection.EAST, maxX, y, z, (short) (zSize / 2), ySize,
+ color, adjOverlapEast, skyLightTop, blockLight);
+ makeAdjQuads(builder, adjDataEast[1], LodDirection.EAST, maxX, y, (short) (z + zSize / 2), (short) (zSize / 2), ySize,
+ color, adjOverlapEast, skyLightTop, blockLight);
+ }
+ }
+ }
+
+ private static void makeAdjQuads(LodQuadBuilder builder, ColumnArrayView adjData, LodDirection direction, short x, short y,
+ short z, short w0, short wy, int color, int overlapColor, byte upSkyLight, byte blockLight)
+ {
+ color = ColorUtil.applyShade(color, MC.getShade(direction));
+ ColumnArrayView dataPoint = adjData;
+ if (dataPoint == null || DataPointUtil.isVoid(dataPoint.get(0)))
+ {
+ builder.addQuadAdj(direction, x, y, z, w0, wy, color, (byte) 15, blockLight);
+ return;
+ }
+
+ int i;
+ boolean firstFace = true;
+ boolean allAbove = true;
+ short previousDepth = -1;
+ byte nextSkyLight = upSkyLight;
+
+ // TODO transparency ocean floor fix
+ // boolean isOpaque = ((colorMap[0] >> 24) & 0xFF) == 255;
+ for (i = 0; i < dataPoint.size() && DataPointUtil.doesItExist(adjData.get(i))
+ && !DataPointUtil.isVoid(adjData.get(i)); i++)
+ {
+ long adjPoint = adjData.get(i);
+
+ // TODO transparency ocean floor fix
+ // if (isOpaque && DataPointUtil.getAlpha(singleAdjDataPoint) != 255)
+ // continue;
+
+ short height = DataPointUtil.getHeight(adjPoint);
+ short depth = DataPointUtil.getDepth(adjPoint);
+
+ // If the depth of said block is higher than our max Y, continue
+ // Basically: y < maxY <= _____ height
+ // _______&&: y < maxY <= depth
+ if (y + wy <= depth)
+ continue;
+ // Now: depth < maxY
+ allAbove = false;
+
+ if (height < y)
+ {
+ // Basically: _____ height < y < maxY
+ // _______&&: depth ______ < y < maxY
+ if (firstFace)
+ {
+ builder.addQuadAdj(direction, x, y, z, w0, wy, color, DataPointUtil.getLightSky(adjPoint),
+ blockLight);
+ }
+ else
+ {
+ // Now: depth < height < y < previousDepth < maxY
+ if (previousDepth == -1)
+ throw new RuntimeException("Loop error");
+ builder.addQuadAdj(direction, x, y, z, w0, (short) (previousDepth - y), color,
+ DataPointUtil.getLightSky(adjPoint), blockLight);
+ previousDepth = -1;
+ }
+ break;
+ }
+
+ if (depth <= y)
+ { // AND y <= height
+ if (y + wy <= height)
+ {
+ // Basically: ________ y < maxY <= height
+ // _______&&: depth <= y < maxY
+ // The face is inside adj face completely.
+ if (overlapColor != 0)
+ {
+ builder.addQuadAdj(direction, x, y, z, w0, wy, overlapColor, (byte) 15, (byte) 15);
+ }
+ break;
+ }
+ // Otherwise: ________ y <= Height < maxY
+ // _______&&: depth <= y _________ < maxY
+ // the adj data intersects the lower part of the current data
+ if (height > y && overlapColor != 0)
+ {
+ builder.addQuadAdj(direction, x, y, z, w0, (short) (height - y), overlapColor, (byte) 15, (byte) 15);
+ }
+ // if this is the only face, use the maxY and break,
+ // if there was another face we finish the last one and break
+ if (firstFace)
+ {
+ builder.addQuadAdj(direction, x, height, z, w0, (short) (y + wy - height), color,
+ DataPointUtil.getLightSky(adjPoint), blockLight);
+ }
+ else
+ {
+ // Now: depth <= y <= height <= previousDepth < maxY
+ if (previousDepth == -1)
+ throw new RuntimeException("Loop error");
+ if (previousDepth > height)
+ {
+ builder.addQuadAdj(direction, x, height, z, w0, (short) (previousDepth - height), color,
+ DataPointUtil.getLightSky(adjPoint), blockLight);
+ }
+ previousDepth = -1;
+ }
+ break;
+ }
+
+ // In here always true: y < depth < maxY
+ // _________________&&: y < _____ (height and maxY)
+
+ if (y + wy <= height)
+ {
+ // Basically: y _______ < maxY <= height
+ // _______&&: y < depth < maxY
+ // the adj data intersects the higher part of the current data
+ if (overlapColor != 0)
+ {
+ builder.addQuadAdj(direction, x, depth, z, w0, (short) (y + wy - depth), overlapColor, (byte) 15, (byte) 15);
+ }
+ // we start the creation of a new face
+ }
+ else
+ {
+ // Otherwise: y < _____ height < maxY
+ // _______&&: y < depth ______ < maxY
+ if (overlapColor != 0)
+ {
+ builder.addQuadAdj(direction, x, depth, z, w0, (short) (height - depth), overlapColor, (byte) 15, (byte) 15);
+ }
+ if (firstFace)
+ {
+ builder.addQuadAdj(direction, x, height, z, w0, (short) (y + wy - height), color,
+ DataPointUtil.getLightSky(adjPoint), blockLight);
+ }
+ else
+ {
+ // Now: y < depth < height <= previousDepth < maxY
+ if (previousDepth == -1)
+ throw new RuntimeException("Loop error");
+ if (previousDepth > height)
+ {
+ builder.addQuadAdj(direction, x, height, z, w0, (short) (previousDepth - height), color,
+ DataPointUtil.getLightSky(adjPoint), blockLight);
+ }
+ previousDepth = -1;
+ }
+ }
+ // set next top as current depth
+ previousDepth = depth;
+ firstFace = false;
+ nextSkyLight = upSkyLight;
+ if (i + 1 < adjData.size() && DataPointUtil.doesItExist(adjData.get(i + 1)))
+ nextSkyLight = DataPointUtil.getLightSky(adjData.get(i + 1));
+ }
+
+ if (allAbove)
+ {
+ builder.addQuadAdj(direction, x, y, z, w0, wy, color, upSkyLight, blockLight);
+ }
+ else if (previousDepth != -1)
+ {
+ // We need to finish the last quad.
+ builder.addQuadAdj(direction, x, y, z, w0, (short) (previousDepth - y), color, nextSkyLight,
+ blockLight);
+ }
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java
index 92533a655..adf2b807d 100644
--- a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java
+++ b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java
@@ -1,12 +1,15 @@
package com.seibel.lod.core.objects.a7.datatype.column;
+import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.objects.LodDataView;
import com.seibel.lod.core.objects.a7.DHLevel;
import com.seibel.lod.core.objects.a7.LodQuadTree;
+import com.seibel.lod.core.objects.a7.LodSection;
import com.seibel.lod.core.objects.a7.data.DataSourceLoader;
import com.seibel.lod.core.objects.a7.pos.DhSectionPos;
import com.seibel.lod.core.objects.a7.render.RenderDataSource;
-import com.seibel.lod.core.objects.opengl.RenderBuffer;
+import com.seibel.lod.core.objects.a7.render.RenderBuffer;
+import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import java.io.DataInputStream;
@@ -14,6 +17,9 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
public class ColumnDatatype implements RenderDataSource, IColumnDatatype {
@@ -251,28 +257,57 @@ public class ColumnDatatype implements RenderDataSource, IColumnDatatype {
return SECTION_SIZE_OFFSET;
}
- @Override
- public void enableRender() {
+ private CompletableFuture inBuildRenderBuffer = null;
+
+ private void tryBuildBuffer(LodQuadTree quadTree) {
+ if (inBuildRenderBuffer == null) {
+ ColumnDatatype[] data = new ColumnDatatype[LodDirection.ADJ_DIRECTIONS.length];
+ for (LodDirection direction : LodDirection.ADJ_DIRECTIONS) {
+ LodSection section = quadTree.getSection(sectionPos.getAdjacent(direction)); //FIXME: Handle traveling through different detail levels
+ if (section.getRenderContainer() != null && section.getRenderContainer() instanceof ColumnRenderBuffer) {
+ data[direction.ordinal()-2] = ((ColumnDatatype) section.getRenderContainer());
+ }
+ }
+ inBuildRenderBuffer = ColumnRenderBuffer.build( this, data);
+ }
+ }
+ private void cancelBuildBuffer() {
+ if (inBuildRenderBuffer != null) {
+ inBuildRenderBuffer.cancel(false);
+ inBuildRenderBuffer = null;
+ }
+ }
+ @Override
+ public void enableRender(LodQuadTree quadTree) {
+ tryBuildBuffer(quadTree);
}
@Override
public void disableRender() {
-
+ cancelBuildBuffer();
}
@Override
public boolean isRenderReady() {
- return false;
+ return (inBuildRenderBuffer != null && inBuildRenderBuffer.isDone());
}
@Override
public void dispose() {
+ cancelBuildBuffer();
}
@Override
- public boolean trySwapRenderBuffer(AtomicReference referenceSlot) {
+ public boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference referenceSlot) {
+ if (inBuildRenderBuffer != null && inBuildRenderBuffer.isDone()) {
+ referenceSlot.set(inBuildRenderBuffer.join());
+ inBuildRenderBuffer = null;
+ return true;
+ } else {
+ tryBuildBuffer(quadTree);
+ }
return false;
}
diff --git a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnRenderBuffer.java b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnRenderBuffer.java
index d06458b01..4650c9baa 100644
--- a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnRenderBuffer.java
+++ b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnRenderBuffer.java
@@ -1,5 +1,335 @@
package com.seibel.lod.core.objects.a7.datatype.column;
-public class ColumnRenderBuffer {
+import com.seibel.lod.core.Config;
+import com.seibel.lod.core.api.internal.ClientApi;
+import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
+import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.CubicLodTemplate;
+import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory;
+import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
+import com.seibel.lod.core.enums.LodDirection;
+import com.seibel.lod.core.enums.config.GpuUploadMethod;
+import com.seibel.lod.core.enums.rendering.DebugMode;
+import com.seibel.lod.core.enums.rendering.GLProxyContext;
+import com.seibel.lod.core.logging.ConfigBasedLogger;
+import com.seibel.lod.core.logging.DhLoggerBuilder;
+import com.seibel.lod.core.objects.a7.render.RenderBuffer;
+import com.seibel.lod.core.render.GLProxy;
+import com.seibel.lod.core.render.LodRenderProgram;
+import com.seibel.lod.core.render.LodRenderer;
+import com.seibel.lod.core.render.objects.GLVertexBuffer;
+import com.seibel.lod.core.util.*;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.lwjgl.opengl.GL32;
+import java.lang.invoke.MethodHandles;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import static com.seibel.lod.core.render.GLProxy.GL_LOGGER;
+import static com.seibel.lod.core.render.LodRenderer.EVENT_LOGGER;
+
+
+public class ColumnRenderBuffer extends RenderBuffer {
+ //TODO: Make the pool use configurable number of threads
+ public static final ExecutorService BUFFER_BUILDERS =
+ Executors.newCachedThreadPool(new LodThreadFactory("ColumnBufferBuilders", 5));
+ public static final ExecutorService BUFFER_UPLOADER = LodUtil.makeSingleThreadPool("ColumnBufferUploader");
+
+ public static final ConfigBasedLogger EVENT_LOGGER = new ConfigBasedLogger(LogManager.getLogger(LodRenderer.class),
+ () -> Config.Client.Advanced.Debugging.DebugSwitch.logRendererBufferEvent.get());
+ 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;
+
+ public ColumnRenderBuffer() {
+ vbos = new GLVertexBuffer[0];
+ }
+
+
+ private void _uploadBuffersDirect(LodQuadBuilder builder, GpuUploadMethod method) throws InterruptedException {
+ resize(builder.getCurrentNeededVertexBufferCount());
+ long remainingNS = 0;
+ long BPerNS = Config.Client.Advanced.Buffers.gpuUploadPerMegabyteInMilliseconds.get();
+
+ int i = 0;
+ Iterator 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);
+ int size = bb.limit() - bb.position();
+ try {
+ 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();
+ LOGGER.error("Failed to upload buffer: ", e);
+ }
+ if (BPerNS<=0) continue;
+ // upload buffers over an extended period of time
+ // to hopefully prevent stuttering.
+ remainingNS += size * BPerNS;
+ if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS)) {
+ if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
+ remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
+ Thread.sleep(remainingNS / 1000000, (int) (remainingNS % 1000000));
+ remainingNS = 0;
+ }
+ }
+ if (i < vbos.length) {
+ throw new RuntimeException("Too few vertex buffers!!");
+ }
+ }
+
+ private void _uploadBuffersMapped(LodQuadBuilder builder, GpuUploadMethod method)
+ {
+ resize(builder.getCurrentNeededVertexBufferCount());
+ for (int i=0; i size) {
+ for (int i=size; i {
+ for (GLVertexBuffer b : vbos) {
+ if (b == null) continue;
+ b.destroy(false);
+ }
+ });
+ }
+
+
+ public static CompletableFuture build(ColumnDatatype data, ColumnDatatype[] adjData) {
+ EVENT_LOGGER.trace("RenderRegion startBuild @ {}", data.sectionPos);
+ return CompletableFuture.supplyAsync(() -> {
+ try {
+ EVENT_LOGGER.trace("RenderRegion start QuadBuild @ {}", data.sectionPos);
+ int skyLightCullingBelow = Config.Client.Graphics.AdvancedGraphics.caveCullingHeight.get();
+ // FIXME: Clamp also to the max world height.
+ skyLightCullingBelow = Math.max(skyLightCullingBelow, LodBuilder.MIN_WORLD_HEIGHT);
+ LodQuadBuilder builder = new LodQuadBuilder(true, skyLightCullingBelow);
+ Runnable buildRun = ()->{
+ makeLodRenderData(builder, data, adjData);
+ };
+ buildRun.run();
+ EVENT_LOGGER.trace("RenderRegion end QuadBuild @ {}", data.sectionPos);
+ return builder;
+ } catch (Throwable e3) {
+ EVENT_LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3);
+ throw e3;
+ }
+ }, BUFFER_BUILDERS)
+ .thenApplyAsync((builder) -> {
+ try {
+ EVENT_LOGGER.trace("RenderRegion start Upload @ {}", data.sectionPos);
+ GLProxy glProxy = GLProxy.getInstance();
+ GpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
+ GLProxyContext oldContext = glProxy.getGlContext();
+ glProxy.setGlContext(GLProxyContext.LOD_BUILDER);
+ ColumnRenderBuffer buffer = new ColumnRenderBuffer();
+ try {
+ buffer.uploadBuffer(builder, method);
+ } finally {
+ glProxy.setGlContext(oldContext); //FIXME: Close buffer on exception?
+ }
+ EVENT_LOGGER.trace("RenderRegion end Upload @ {}", data.sectionPos);
+ return buffer;
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (Throwable e3) {
+ EVENT_LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
+ throw e3;
+ }
+ }, BUFFER_UPLOADER).handle((v, e) -> {
+ if (e != null) {
+ return null;
+ } else {
+ return v;
+ }
+ });
+ }
+
+
+
+ private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnDatatype region, ColumnDatatype[] adjRegions) {
+
+ // Variable initialization
+ DebugMode debugMode = Config.Client.Advanced.Debugging.debugMode.get();
+
+ byte detailLevel = region.getDataDetail();
+ int dataSize = 1 << detailLevel;
+ for (int x = 0; x < dataSize; x++) {
+ for (int z = 0; z < dataSize; z++) {
+ ColumnArrayView posData = region.getVerticalDataView(x, z);
+ if (posData.size() == 0 || !DataPointUtil.doesItExist(posData.get(0))
+ || DataPointUtil.isVoid(posData.get(0)))
+ continue;
+
+ ColumnArrayView[][] adjData = new ColumnArrayView[4][];
+ // We extract the adj data in the four cardinal direction
+
+ // we first reset the adjShadeDisabled. This is used to disable the shade on the
+ // border when we have transparent block like water or glass
+ // to avoid having a "darker border" underground
+ // Arrays.fill(adjShadeDisabled, false);
+
+ // We check every adj block in each direction
+
+ // If the adj block is rendered in the same region and with same detail
+ // and is positioned in a place that is not going to be rendered by vanilla game
+ // then we can set this position as adj
+ // We avoid cases where the adjPosition is in player chunk while the position is
+ // not
+ // to always have a wall underwater
+ for (LodDirection lodDirection : LodDirection.ADJ_DIRECTIONS) {
+ try {
+ int xAdj = x + lodDirection.getNormal().x;
+ int zAdj = z + lodDirection.getNormal().z;
+ boolean isCrossRegionBoundary = (xAdj < 0 || xAdj >= dataSize) ||
+ (zAdj < 0 || zAdj >= dataSize);
+ ColumnDatatype adjRegion;
+ byte adjDetail;
+
+ //we check if the detail of the adjPos is equal to the correct one (region border fix)
+ //or if the detail is wrong by 1 value (region+circle border fix)
+ if (isCrossRegionBoundary) {
+ //we compute at which detail that position should be rendered
+ adjRegion = adjRegions[lodDirection.ordinal()-2];
+ if(adjRegion == null) continue;
+ adjDetail = adjRegion.getDataDetail();
+ if (adjDetail != detailLevel) {
+ //TODO: Implement this
+ } else {
+ if (xAdj < 0) xAdj += dataSize;
+ if (zAdj < 0) zAdj += dataSize;
+ if (xAdj >= dataSize) xAdj -= dataSize;
+ if (zAdj >= dataSize) zAdj -= dataSize;
+ }
+ } else {
+ adjRegion = region;
+ adjDetail = detailLevel;
+ }
+
+ if (adjDetail < detailLevel-1 || adjDetail > detailLevel+1) {
+ continue;
+ }
+
+ if (adjDetail == detailLevel || adjDetail > detailLevel) {
+ adjData[lodDirection.ordinal() - 2] = new ColumnArrayView[1];
+ adjData[lodDirection.ordinal() - 2][0] = adjRegion.getVerticalDataView(xAdj, zAdj);
+ } else {
+ adjData[lodDirection.ordinal() - 2] = new ColumnArrayView[2];
+ adjData[lodDirection.ordinal() - 2][0] = adjRegion.getVerticalDataView(xAdj, zAdj);
+ adjData[lodDirection.ordinal() - 2][1] = adjRegion.getVerticalDataView(
+ xAdj + (lodDirection.getAxis()==LodDirection.Axis.X ? 0 : 1),
+ zAdj + (lodDirection.getAxis()==LodDirection.Axis.Z ? 0 : 1));
+ }
+ } catch (RuntimeException e) {
+ EVENT_LOGGER.warn("Failed to get adj data for [{}:{},{}] at [{}]", detailLevel, x, z, lodDirection);
+ EVENT_LOGGER.warn("Detail exception: ", e);
+ }
+ }
+
+ // We render every vertical lod present in this position
+ // We only stop when we find a block that is void or non-existing block
+ for (int i = 0; i < posData.size(); i++) {
+ long data = posData.get(i);
+ // If the data is not renderable (Void or non-existing) we stop since there is
+ // no data left in this position
+ if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data))
+ break;
+
+ long adjDataTop = i - 1 >= 0 ? posData.get(i - 1) : DataPointUtil.EMPTY_DATA;
+ long adjDataBot = i + 1 < posData.size() ? posData.get(i + 1) : DataPointUtil.EMPTY_DATA;
+
+ // We send the call to create the vertices
+ CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, detailLevel,
+ x, z, quadBuilder, debugMode);
+ }
+ }
+ }
+ quadBuilder.mergeQuads();
+ }
}
diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java b/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java
index e1d62345d..447e611ae 100644
--- a/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java
+++ b/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java
@@ -1,8 +1,9 @@
package com.seibel.lod.core.objects.a7.render;
+import com.seibel.lod.core.objects.a7.LodQuadTree;
import com.seibel.lod.core.objects.a7.data.LodDataSource;
import com.seibel.lod.core.objects.a7.pos.DhSectionPos;
-import com.seibel.lod.core.objects.opengl.RenderBuffer;
+import com.seibel.lod.core.objects.a7.render.RenderBuffer;
import java.util.concurrent.atomic.AtomicReference;
@@ -11,23 +12,34 @@ public class EmptyRenderContainer implements RenderDataSource {
// NOTE: No register() needed since this should never be loaded from a actual data.
+
@Override
- public void load() {
+ public void enableRender(LodQuadTree quadTree) {
}
@Override
- public void unload() {
+ public void disableRender() {
}
+ @Override
+ public boolean isRenderReady() {
+ return false;
+ }
+
@Override
public void dispose() {
}
@Override
- public boolean trySwapRenderBuffer(AtomicReference referenceSlot) {
+ public byte getDetailOffset() {
+ return 0;
+ }
+
+ @Override
+ public boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference referenceSlot) {
return false; // no swap
}
}
diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBuffer.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBuffer.java
new file mode 100644
index 000000000..54b7c5879
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBuffer.java
@@ -0,0 +1,56 @@
+/*
+ * This file is part of the Distant Horizons mod (formerly the LOD Mod),
+ * licensed under the GNU LGPL 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 Lesser 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.seibel.lod.core.objects.a7.render;
+
+import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
+import com.seibel.lod.core.enums.config.GpuUploadMethod;
+import com.seibel.lod.core.render.LodRenderProgram;
+import com.seibel.lod.core.util.StatsMap;
+
+import java.util.ConcurrentModificationException;
+
+public abstract class RenderBuffer implements AutoCloseable
+{
+ // ======================================================================
+ // ====================== Methods for implementations ===================
+ // ======================================================================
+
+ // ========== Called by render thread ==========
+ /* Called on... well... rendering.
+ * Return false if nothing rendered. (Optional) */
+ public abstract boolean render(LodRenderProgram shaderProgram);
+
+ // ========== Called by any thread. (thread safe) ==========
+
+ /* Called by anyone. This method is allowed to throw exceptions, but
+ * are never allowed to modify any values. This should behave the same
+ * to other methods as if the method have never been called.
+ * Note: This method is PURELY for debug or stats logging ONLY! */
+ public abstract void debugDumpStats(StatsMap statsMap);
+
+ // ========= Called only when 1 thread is using it =======
+ /* This method is called when object is no longer in use.
+ * Called either after uploadBuffers() returned false (On buffer Upload
+ * thread), or by others when the object is not being used. (not in build,
+ * upload, or render state). */
+ public abstract void close();
+
+
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java
index f9a4340f7..3659dc4ce 100644
--- a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java
+++ b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java
@@ -4,7 +4,7 @@ import com.seibel.lod.core.objects.Pos2D;
import com.seibel.lod.core.objects.a7.LodQuadTree;
import com.seibel.lod.core.objects.a7.LodSection;
import com.seibel.lod.core.objects.a7.pos.DhSectionPos;
-import com.seibel.lod.core.objects.opengl.RenderBuffer;
+import com.seibel.lod.core.objects.a7.render.RenderBuffer;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.gridList.MovableGridRingList;
@@ -57,7 +57,7 @@ public class RenderBufferHandler {
}
} else {
LodUtil.assertTrue(container != null); // section.isLoaded() should have ensured this
- container.trySwapRenderBuffer(renderBufferSlot);
+ container.trySwapRenderBuffer(target, renderBufferSlot);
}
// Update children's render buffer state
diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSource.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSource.java
index 1596b06e5..01e9ba842 100644
--- a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSource.java
+++ b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSource.java
@@ -1,6 +1,7 @@
package com.seibel.lod.core.objects.a7.render;
-import com.seibel.lod.core.objects.opengl.RenderBuffer;
+import com.seibel.lod.core.objects.a7.LodQuadTree;
+import com.seibel.lod.core.objects.a7.render.RenderBuffer;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
@@ -43,7 +44,7 @@ public interface RenderDataSource {
// return null;
// }
- void enableRender();
+ void enableRender(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)
@@ -56,6 +57,6 @@ public interface RenderDataSource {
* @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(AtomicReference referenceSlot);
+ boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference referenceSlot);
}
diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java b/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java
index 0d7c0c1ce..845ce0b02 100644
--- a/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java
+++ b/src/main/java/com/seibel/lod/core/objects/opengl/RenderRegion.java
@@ -252,18 +252,6 @@ public class RenderRegion implements AutoCloseable
});
}
- private static final int ADJACENT8[][] = {
- {-1,-1},
- {-1, 0},
- {-1, 1},
- { 0,-1},
- //{ 0, 0},
- { 0, 1},
- { 1,-1},
- { 1, 0},
- { 1, 1}
- };
-
private static void makeLodRenderData(LodQuadBuilder quadBuilder, LodRegion region, LodRegion[] adjRegions, int playerX,
int playerZ) {
byte minDetail = region.getMinDetailLevel();