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();