First I think somewhat prob should work renderbuffer impl?

This commit is contained in:
TomTheFurry
2022-05-25 22:44:47 +08:00
parent 987dfbc87f
commit a10e82651d
11 changed files with 833 additions and 34 deletions
@@ -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
}
}
@@ -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);
}
});
}
@@ -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);
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
}
@@ -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<ColumnRenderBuffer> 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<RenderBuffer> referenceSlot) {
public boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference<RenderBuffer> referenceSlot) {
if (inBuildRenderBuffer != null && inBuildRenderBuffer.isDone()) {
referenceSlot.set(inBuildRenderBuffer.join());
inBuildRenderBuffer = null;
return true;
} else {
tryBuildBuffer(quadTree);
}
return false;
}
@@ -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<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);
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<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++])) {}
}
private GLVertexBuffer getOrMakeVbo(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 boolean uploadBuffer(LodQuadBuilder builder, GpuUploadMethod method) throws InterruptedException {
if (method.useEarlyMapping) {
_uploadBuffersMapped(builder, method);
} else {
_uploadBuffersDirect(builder, method);
}
return true;
}
@Override
public boolean render(LodRenderProgram shaderProgram) {
boolean hasRendered = false;
for (GLVertexBuffer vbo : vbos) {
if (vbo == null) continue;
if (vbo.getVertexCount() == 0) continue;
hasRendered = true;
vbo.bind();
shaderProgram.bindVertexBuffer(vbo.getId());
if (LodRenderer.ENABLE_IBO) {
GL32.glDrawElements(GL32.GL_TRIANGLES, (vbo.getVertexCount()/4)*6, ClientApi.renderer.quadIBO.getType(), 0);
} else {
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbo.getVertexCount());
}
//LodRenderer.tickLogger.info("Vertex buffer: {}", vbo);
}
return hasRendered;
}
@Override
public void debugDumpStats(StatsMap statsMap) {
statsMap.incStat("RenderBuffers");
statsMap.incStat("SimpleRenderBuffers");
for (GLVertexBuffer b : vbos) {
if (b == null) continue;
statsMap.incStat("VBOs");
if (b.getSize() == LodBufferBuilderFactory.FULL_SIZED_BUFFER) {
statsMap.incStat("FullsizedVBOs");
}
if (b.getSize() == 0) GL_LOGGER.warn("VBO with size 0");
statsMap.incBytesStat("TotalUsage", b.getSize());
}
}
@Override
public void close() {
GLProxy.getInstance().recordOpenGlCall(() -> {
for (GLVertexBuffer b : vbos) {
if (b == null) continue;
b.destroy(false);
}
});
}
public static CompletableFuture<ColumnRenderBuffer> 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();
}
}
@@ -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<RenderBuffer> referenceSlot) {
public byte getDetailOffset() {
return 0;
}
@Override
public boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference<RenderBuffer> referenceSlot) {
return false; // no swap
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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();
}
@@ -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
@@ -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<RenderBuffer> referenceSlot);
boolean trySwapRenderBuffer(LodQuadTree quadTree, AtomicReference<RenderBuffer> referenceSlot);
}
@@ -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();