This commit is contained in:
s809
2024-08-11 20:59:23 +05:00
52 changed files with 779 additions and 1601 deletions
@@ -23,13 +23,12 @@ package com.seibel.distanthorizons.api.enums.config;
* AUTO, <br>
* BUFFER_STORAGE, <br>
* SUB_DATA, <br>
* BUFFER_MAPPING, <br>
* DATA <br>
*
* @author Leetom
* @author James Seibel
* @version 2024-4-6
* @since API 2.0.0
* @since API 3.0.0
*/
public enum EDhApiGpuUploadMethod
{
@@ -49,7 +48,10 @@ public enum EDhApiGpuUploadMethod
* May end up storing buffers in System memory. <br>
* Fast rending if in GPU memory, slow if in system memory, <br>
* but won't stutter when uploading.
*
* @deprecated not currently supported
*/
@Deprecated
BUFFER_MAPPING(true, false),
/** Fast rendering but may stutter when uploading. */
@@ -36,7 +36,6 @@ public interface IDhApiConfig
IDhApiWorldGenerationConfig worldGenerator();
IDhApiMultiplayerConfig multiplayer();
IDhApiMultiThreadingConfig multiThreading();
IDhApiGpuBuffersConfig gpuBuffers();
// note: DON'T add the Auto Updater to this API. We only want the user's to have the ability to control when things are downloaded to their machines.
//IDhApiLoggingConfig logging(); // TODO implement
IDhApiDebuggingConfig debugging();
@@ -1,48 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.api.interfaces.config.client;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
/**
* Distant Horizons' OpenGL buffer configuration.
*
* @author James Seibel
* @version 2023-6-14
* @since API 1.0.0
*/
public interface IDhApiGpuBuffersConfig extends IDhApiConfigGroup
{
/** Defines how geometry data is uploaded to the GPU. */
IDhApiConfigValue<EDhApiGpuUploadMethod> gpuUploadMethod();
/**
* Defines how long we should wait after uploading one
* Megabyte of geometry data to the GPU before uploading
* the next Megabyte of data. <br>
* This can be set to a non-zero number to reduce stuttering caused by
* uploading buffers to the GPU.
*/
IDhApiConfigValue<Integer> gpuUploadPerMegabyteInMilliseconds();
}
@@ -21,8 +21,10 @@ package com.seibel.distanthorizons.api.objects.data;
import com.seibel.distanthorizons.api.interfaces.factories.IDhApiWrapperFactory;
import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
import org.apache.logging.log4j.LogManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -66,7 +68,7 @@ public class DhApiChunk
* @since API 3.0.0
*/
public static DhApiChunk create(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos)
{ return new DhApiChunk(chunkPosX, chunkPosZ, topYBlockPos, bottomYBlockPos, false); }
{ return new DhApiChunk(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos, false); }
/**
* Only visible to internal DH methods
@@ -114,27 +116,50 @@ public class DhApiChunk
*/
public void setDataPoints(int relX, int relZ, List<DhApiTerrainDataPoint> dataPoints) throws IndexOutOfBoundsException, IllegalArgumentException
{
//================//
// validate input //
//================//
int internalArrayIndex = (relZ << 4) | relX;
throwIfRelativePosOutOfBounds(relX, relZ);
// validate the incoming datapoints
if (dataPoints != null)
// ignore empty inputs
if (dataPoints == null)
{
for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation
return;
}
// check that each datapoint is valid
for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation
{
DhApiTerrainDataPoint dataPoint = dataPoints.get(i);
if (dataPoint == null)
{
DhApiTerrainDataPoint dataPoint = dataPoints.get(i);
if (dataPoint == null)
{
throw new IllegalArgumentException("Null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR.");
}
if (dataPoint.detailLevel != 0)
{
throw new IllegalArgumentException("DhApiTerrainDataPoints has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0].");
}
throw new IllegalArgumentException("Null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR.");
}
if (dataPoint.detailLevel != 0)
{
throw new IllegalArgumentException("DhApiTerrainDataPoints has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0].");
}
}
this.dataPoints.set((relZ << 4) | relX, dataPoints);
//================//
// set datapoints //
//================//
// DH expects datapoints to be in a top-down order
DhApiTerrainDataPoint first = dataPoints.get(0);
DhApiTerrainDataPoint last = dataPoints.get(dataPoints.size() - 1);
if (first.bottomYBlockPos < last.bottomYBlockPos)
{
// flip the array if it's in bottom-up order
Collections.reverse(dataPoints);
}
this.dataPoints.set(internalArrayIndex, dataPoints);
}
@@ -24,12 +24,12 @@ import java.util.Arrays;
/**
* Miscellaneous string helper functions.
*
* @author James Seibel
* @version 2022-7-19
*/
public class StringUtil
{
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
/**
* Returns the n-th index of the given string. <br> <br>
*
@@ -67,8 +67,6 @@ public class StringUtil
return stringBuilder.toString();
}
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
/**
* Converts the given byte array into a hex string representation. <br>
* source: https://stackoverflow.com/a/9855338
@@ -85,4 +83,20 @@ public class StringUtil
return new String(hexChars);
}
/**
* Returns a shortened version of the given string that is no longer than maxLength. <br>
* If null returns the empty string.
*/
public static String shortenString(String str, int maxLength)
{
if (str == null)
{
return "";
}
else
{
return str.substring(0, Math.min(str.length(), maxLength));
}
}
}
@@ -42,8 +42,6 @@ public class DhApiConfig implements IDhApiConfig
@Override
public IDhApiMultiThreadingConfig multiThreading() { return DhApiMultiThreadingConfig.INSTANCE; }
@Override
public IDhApiGpuBuffersConfig gpuBuffers() { return DhApiGpuBuffersConfig.INSTANCE; }
@Override
public IDhApiDebuggingConfig debugging() { return DhApiDebuggingConfig.INSTANCE; }
}
@@ -1,42 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiGpuBuffersConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
public class DhApiGpuBuffersConfig implements IDhApiGpuBuffersConfig
{
public static DhApiGpuBuffersConfig INSTANCE = new DhApiGpuBuffersConfig();
private DhApiGpuBuffersConfig() { }
public IDhApiConfigValue<EDhApiGpuUploadMethod> gpuUploadMethod()
{ return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadMethod); }
public IDhApiConfigValue<Integer> gpuUploadPerMegabyteInMilliseconds()
{ return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds); }
}
@@ -386,7 +386,7 @@ public class ClientApi
{
// logging //
this.sendChatMessagesNow();
this.sendQueuedChatMessages();
IProfilerWrapper profiler = MC.getProfiler();
profiler.pop(); // get out of "terrain"
@@ -554,7 +554,7 @@ public class ClientApi
}
}
private void sendChatMessagesNow()
private void sendQueuedChatMessages()
{
// dev build
if (ModInfo.IS_DEV_BUILD && !this.configOverrideReminderPrinted && MC.playerExists())
@@ -562,10 +562,12 @@ public class ClientApi
this.configOverrideReminderPrinted = true;
// remind the user that this is a development build
MC.sendChatMessage("\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r");
MC.sendChatMessage("Issues may occur with this version.");
MC.sendChatMessage("Here be dragons!");
MC.sendChatMessage("");
String message =
// green text
"\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r\n" +
"Issues may occur with this version.\n" +
"Here be dragons!\n";
MC.sendChatMessage(message);
}
// memory
@@ -580,11 +582,13 @@ public class ClientApi
long maxMemoryInBytes = Runtime.getRuntime().maxMemory();
if (maxMemoryInBytes < minimumRecommendedMemoryInBytes)
{
MC.sendChatMessage("\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r");
MC.sendChatMessage("Stuttering or low FPS may occur.");
MC.sendChatMessage("Please increase Minecraft's available memory to 4 gigabytes.");
MC.sendChatMessage("This warning can be disabled in DH's config under Advanced -> Logging.");
MC.sendChatMessage("");
String message =
// orange text
"\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r \n" +
"Stuttering or low FPS may occur. \n" +
"Please increase Minecraft's available memory to 4 gigabytes. \n" +
"This warning can be disabled in DH's config under Advanced -> Logging. \n";
MC.sendChatMessage(message);
}
}
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.IDhServerLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
@@ -31,6 +32,7 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
@@ -46,6 +48,7 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
/** Contains code and variables used by both {@link ClientApi} and {@link ServerApi} */
public class SharedApi
@@ -333,36 +336,50 @@ public class SharedApi
}
// Save or populate the chunk wrapper's lighting
// this is done so we don't have to worry about MC unloading the lighting data for this chunk
boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get();
if (!onlyUseDhLighting && chunkWrapper.isLightCorrect())
// chunk light baking is disabled since profiling revealed it used
// roughly the same amount of time as generating the lighting ourselves and
// was much more likely to have issues with corrupt (all black or all bright) chunks
boolean tryUsingMcLightingEngine = false;
if (tryUsingMcLightingEngine)
{
try
// Save or populate the chunk wrapper's lighting
// this is done so we don't have to worry about MC unloading the lighting data for this chunk
boolean chunkLightPopulated = false;
boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get();
if (!onlyUseDhLighting && chunkWrapper.isLightCorrect())
{
// If MC's lighting engine isn't thread safe this may cause the server thread to lag
chunkWrapper.bakeDhLightingUsingMcLightingEngine();
chunkLightPopulated = chunkWrapper.bakeDhLightingUsingMcLightingEngine(dhLevel.getLevelWrapper());
if (!chunkLightPopulated)
{
// clear any existing data to prevent partial or corrupt lighting
// when re-generating it
chunkWrapper.clearDhBlockLighting();
chunkWrapper.clearDhSkyLighting();
}
}
catch (IllegalStateException e)
// something went wrong during the baking process so we have to generate the lighting ourselves
if (!chunkLightPopulated)
{
LOGGER.warn("Chunk light baking error: " + e.getMessage(), e);
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
}
}
else
{
// generate the chunk's lighting, using neighboring chunks if present
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? 15 : 0);
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
}
// get this chunk's active beacons
List<BeaconBeamDTO> beaconBeamList = chunkWrapper.getAllActiveBeacons(nearbyChunkList);
dhLevel.setBeaconBeamsForChunk(chunkWrapper.getChunkPos(), beaconBeamList);
dhLevel.updateChunkAsync(chunkWrapper);
dhLevel.setChunkHash(chunkWrapper.getChunkPos(), newChunkHash);
dhLevel.updateChunkAsync(chunkWrapper, newChunkHash);
}
catch (Exception e)
{
@@ -54,7 +54,6 @@ import java.util.List;
* Otherwise, you will have issues where only some of the config entries will exist when your listener is created.
*
* @author coolGi
* @version 2023-7-16
*/
public class Config
@@ -126,7 +125,6 @@ public class Config
public static ConfigCategory multiplayer = new ConfigCategory.Builder().set(Multiplayer.class).build();
public static ConfigCategory lodBuilding = new ConfigCategory.Builder().set(LodBuilding.class).build();
public static ConfigCategory multiThreading = new ConfigCategory.Builder().set(MultiThreading.class).build();
public static ConfigCategory buffers = new ConfigCategory.Builder().set(GpuBuffers.class).build();
public static ConfigCategory autoUpdater = new ConfigCategory.Builder().set(AutoUpdater.class).build();
public static ConfigCategory logging = new ConfigCategory.Builder().set(Logging.class).build();
@@ -187,6 +185,7 @@ public class Config
+ "Lowest Quality: " + EDhApiVerticalQuality.HEIGHT_MAP + "\n"
+ "Highest Quality: " + EDhApiVerticalQuality.EXTREME)
.setPerformance(EConfigEntryPerformance.VERY_HIGH)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<EDhApiHorizontalQuality> horizontalQuality = new ConfigEntry.Builder<EDhApiHorizontalQuality>()
@@ -208,6 +207,7 @@ public class Config
+ EDhApiTransparency.DISABLED + ": LODs will be opaque. \n"
+ "")
.setPerformance(EConfigEntryPerformance.MEDIUM)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<EDhApiBlocksToAvoid> blocksToIgnore = new ConfigEntry.Builder<EDhApiBlocksToAvoid>()
@@ -219,6 +219,7 @@ public class Config
+ EDhApiBlocksToAvoid.NON_COLLIDING + ": Only represent solid blocks in the LODs (tall grass, torches, etc. won't count for a LOD's height) \n"
+ "")
.setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Boolean> tintWithAvoidedBlocks = new ConfigEntry.Builder<Boolean>()
@@ -230,6 +231,7 @@ public class Config
+ "False: skipped blocks will not change color of surface below them. "
+ "")
.setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
// TODO fixme
@@ -595,6 +597,7 @@ public class Config
+ "0 = black \n"
+ "1 = normal \n"
+ "2 = near white")
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Double> saturationMultiplier = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats)
@@ -605,6 +608,7 @@ public class Config
+ "0 = black and white \n"
+ "1 = normal \n"
+ "2 = very saturated")
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Boolean> enableCaveCulling = new ConfigEntry.Builder<Boolean>()
@@ -619,14 +623,15 @@ public class Config
+ "Additional Info: Currently this cull all faces \n"
+ " with skylight value of 0 in dimensions that \n"
+ " does not have a ceiling.")
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
@Deprecated
public static ConfigEntry<Integer> caveCullingHeight = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(-4096, 40, 4096)
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
.setMinDefaultMax(-4096, 60, 4096)
.comment(""
+ "At what Y value should cave culling start?")
+ "At what Y value should cave culling start? \n"
+ "Lower this value if you get walls for areas with 0 light.")
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>()
@@ -664,6 +669,7 @@ public class Config
+ EDhApiLodShading.DISABLED + ": All LOD sides will be rendered with the same brightness. \n"
+ "")
.setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Boolean> disableFrustumCulling = new ConfigEntry.Builder<Boolean>()
@@ -698,6 +704,7 @@ public class Config
+ EDhApiGrassSideRendering.AS_DIRT + ": sides render entirely as dirt. \n"
+ "")
.setPerformance(EConfigEntryPerformance.NONE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
}
@@ -782,8 +789,10 @@ public class Config
+ "")
.build();
@Deprecated
public static ConfigEntry<Boolean> onlyUseDhLightingEngine = new ConfigEntry.Builder<Boolean>()
.set(false)
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
.comment(""
+ "If false LODs will be lit by Minecraft's lighting engine when possible \n"
+ "and fall back to the DH lighting engine only when necessary. \n"
@@ -1195,53 +1204,6 @@ public class Config
.build();
}
public static class GpuBuffers
{
public static ConfigEntry<EDhApiGpuUploadMethod> gpuUploadMethod = new ConfigEntry.Builder<EDhApiGpuUploadMethod>()
.set(EDhApiGpuUploadMethod.AUTO)
.comment(""
+ "What method should be used to upload geometry to the GPU? \n"
+ "\n"
+ EDhApiGpuUploadMethod.AUTO + ": Picks the best option based on the GPU you have. \n"
+ "\n"
+ EDhApiGpuUploadMethod.BUFFER_STORAGE + ": Default if OpenGL 4.5 is supported. \n"
+ " Fast rendering, no stuttering. \n"
+ "\n"
+ EDhApiGpuUploadMethod.SUB_DATA + ": Backup option for NVIDIA. \n"
+ " Fast rendering but may stutter when uploading. \n"
+ "\n"
+ EDhApiGpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. \n"
+ " Generally the best option for integrated GPUs. \n"
+ " Default option for AMD/Intel if OpenGL 4.5 isn't supported. \n"
+ " May end up storing buffers in System memory. \n"
+ " Fast rendering if in GPU memory, slow if in system memory, \n"
+ " but won't stutter when uploading. \n"
+ "\n"
+ EDhApiGpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n"
+ " Backup option for AMD/Intel. \n"
+ " Fast rendering but may stutter when uploading. \n"
+ "\n"
+ "If you don't see any difference when changing these settings, \n"
+ "or the world looks corrupted: restart your game."
+ "")
.build();
public static ConfigEntry<Integer> gpuUploadPerMegabyteInMilliseconds = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0, 0, 50)
.comment(""
+ "How long should a buffer wait per Megabyte of data uploaded? \n"
+ "Helpful resource for frame times: https://fpstoms.com \n"
+ "\n"
+ "Longer times may reduce stuttering but will make LODs \n"
+ "transition and load slower. Change this to [0] for no timeout. \n"
+ "\n"
+ "NOTE:\n"
+ "Before changing this config, try changing the \"GPU Upload method\" first. \n"
+ "")
.build();
}
public static class AutoUpdater
{
public static ConfigEntry<Boolean> enableAutoUpdater = new ConfigEntry.Builder<Boolean>()
@@ -1274,7 +1236,7 @@ public class Config
// TODO default to error chat and info file
public static ConfigEntry<EDhApiLoggerMode> logWorldGenEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logWorldGenEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the world generation process. \n"
+ "This can be useful for debugging.")
@@ -1282,7 +1244,7 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logWorldGenPerformance = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logWorldGenPerformance")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log performance about the world generation process. \n"
+ "This can be useful for debugging.")
@@ -1290,7 +1252,7 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logWorldGenLoadEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logWorldGenPerformance")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the world generation process. \n"
+ "This can be useful for debugging.")
@@ -1298,21 +1260,21 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logLodBuilderEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logLodBuilderEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the LOD generation process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerMode> logRendererBufferEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the renderer buffer process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerMode> logRendererGLEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about the renderer OpenGL process. \n"
+ "This can be useful for debugging.")
@@ -1320,7 +1282,7 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logFileReadWriteEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logFileReadWriteEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about file read/write operations. \n"
+ "This can be useful for debugging.")
@@ -1328,7 +1290,7 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logFileSubDimEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logFileSubDimEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about file sub-dimension operations. \n"
+ "This can be useful for debugging.")
@@ -1336,7 +1298,7 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logNetworkEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about network operations. \n"
+ "This can be useful for debugging.")
@@ -1357,6 +1319,13 @@ public class Config
+ "giving some basic information about how DH will function.")
.build();
public static ConfigEntry<Boolean> showModCompatibilityWarningsOnStartup = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If enabled, a chat message will be displayed when a potentially problematic \n"
+ "mod is installed alongside DH.")
.build();
}
public static class Debugging
@@ -1443,38 +1412,38 @@ public class Config
public static ConfigEntry<Boolean> columnBuilderDebugEnable = new ConfigEntry.Builder<Boolean>()
.set(false)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(DebugColumnConfigEventHandler.INSTANCE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugDetailLevel = new ConfigEntry.Builder<Integer>()
.set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(DebugColumnConfigEventHandler.INSTANCE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugXPos = new ConfigEntry.Builder<Integer>()
.set(0)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(DebugColumnConfigEventHandler.INSTANCE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugZPos = new ConfigEntry.Builder<Integer>()
.set(0)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(DebugColumnConfigEventHandler.INSTANCE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugXRow = new ConfigEntry.Builder<Integer>()
.set(-1)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(DebugColumnConfigEventHandler.INSTANCE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugZRow = new ConfigEntry.Builder<Integer>()
.set(-1)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(DebugColumnConfigEventHandler.INSTANCE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
public static ConfigEntry<Integer> columnBuilderDebugColumnIndex = new ConfigEntry.Builder<Integer>()
.set(-1)
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.addListener(DebugColumnConfigEventHandler.INSTANCE)
.addListener(ReloadLodsConfigEventHandler.INSTANCE)
.build();
@@ -23,9 +23,9 @@ import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
public class DebugColumnConfigEventHandler implements IConfigListener
public class ReloadLodsConfigEventHandler implements IConfigListener
{
public static DebugColumnConfigEventHandler INSTANCE = new DebugColumnConfigEventHandler();
public static ReloadLodsConfigEventHandler INSTANCE = new ReloadLodsConfigEventHandler();
@Override
public void onConfigValueSet()
@@ -170,7 +170,7 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
if (newPreset != currentPreset)
{
this.getPresetConfigEntry().set(this.getCustomPresetEnum());
this.getPresetConfigEntry().set(newPreset);
}
}
@@ -19,8 +19,10 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
@@ -61,11 +63,11 @@ public class ColumnBox
//=========//
public static void addBoxQuadsToBuilder(
LodQuadBuilder builder,
LodQuadBuilder builder, IDhClientLevel clientLevel,
short xSize, short ySize, short zSize,
short x, short minY, short z,
int color, byte irisBlockMaterialId, byte skyLight, byte blockLight,
long topData, long bottomData, ColumnArrayView[] adjData)
long topData, long bottomData, ColumnArrayView[] adjData, boolean[] isAdjDataSameDetailLevel)
{
//================//
// variable setup //
@@ -82,6 +84,15 @@ public class ColumnBox
boolean isTopTransparent = RenderDataPointUtil.getAlpha(topData) < 255 && LodRenderer.transparencyEnabled;
boolean isBottomTransparent = RenderDataPointUtil.getAlpha(bottomData) < 255 && LodRenderer.transparencyEnabled;
// defaulting to a value far below what we can normally render means we
// don't need to have an additional "is cave culling enabled" check
int caveCullingMaxY = Integer.MIN_VALUE;
if (Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get())
{
caveCullingMaxY = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get() - clientLevel.getMinY();
}
// if there isn't any data below this LOD, make this LOD's color opaque to prevent seeing void through transparent blocks
// Note: this LOD should still be considered transparent for this method's checks, otherwise rendering bugs may occur
@@ -135,12 +146,11 @@ public class ColumnBox
// NORTH face
{
ColumnArrayView adjCol = adjData[EDhDirection.NORTH.ordinal() - 2]; // TODO can we use something other than ordinal-2?
// if the adjacent column is null that generally means it's representing a different detail level
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.NORTH.ordinal() - 2];
// if the adjacent column is null that generally means the adjacent area hasn't been generated yet
if (adjCol == null)
{
// Add an adjacent face if this is opaque face or transparent over the void.
// By skipping transparent faces that aren't over the void we prevent adding ocean faces
// between detail levels.
if (!isTransparent || overVoid)
{
builder.addQuadAdj(EDhDirection.NORTH, x, minY, z, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
@@ -148,7 +158,7 @@ public class ColumnBox
}
else
{
makeAdjVerticalQuad(builder, adjCol, EDhDirection.NORTH, x, minY, z, xSize, ySize,
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH, x, minY, z, xSize, ySize,
color, irisBlockMaterialId, blockLight);
}
}
@@ -156,6 +166,7 @@ public class ColumnBox
// SOUTH face
{
ColumnArrayView adjCol = adjData[EDhDirection.SOUTH.ordinal() - 2];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.SOUTH.ordinal() - 2];
if (adjCol == null)
{
if (!isTransparent || overVoid)
@@ -165,7 +176,7 @@ public class ColumnBox
}
else
{
makeAdjVerticalQuad(builder, adjCol, EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize,
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize,
color, irisBlockMaterialId, blockLight);
}
}
@@ -173,6 +184,7 @@ public class ColumnBox
// WEST face
{
ColumnArrayView adjCol = adjData[EDhDirection.WEST.ordinal() - 2];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.WEST.ordinal() - 2];
if (adjCol == null)
{
if (!isTransparent || overVoid)
@@ -182,7 +194,7 @@ public class ColumnBox
}
else
{
makeAdjVerticalQuad(builder, adjCol, EDhDirection.WEST, x, minY, z, zSize, ySize,
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST, x, minY, z, zSize, ySize,
color, irisBlockMaterialId, blockLight);
}
}
@@ -190,6 +202,7 @@ public class ColumnBox
// EAST face
{
ColumnArrayView adjCol = adjData[EDhDirection.EAST.ordinal() - 2];
boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.EAST.ordinal() - 2];
if (adjCol == null)
{
if (!isTransparent || overVoid)
@@ -199,14 +212,14 @@ public class ColumnBox
}
else
{
makeAdjVerticalQuad(builder, adjCol, EDhDirection.EAST, maxX, minY, z, zSize, ySize,
makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST, maxX, minY, z, zSize, ySize,
color, irisBlockMaterialId, blockLight);
}
}
}
private static void makeAdjVerticalQuad(
LodQuadBuilder builder, @NotNull ColumnArrayView adjColumnView, EDhDirection direction,
LodQuadBuilder builder, @NotNull ColumnArrayView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction,
short x, short yMin, short z, short horizontalWidth, short ySize,
int color, byte irisBlockMaterialId, byte blockLight)
{
@@ -283,10 +296,30 @@ public class ColumnBox
{
// adj opaque
// mark positions adjacent is covering
byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint);
for (int i = adjMinY; i < adjMaxY; i++)
{
byte skyLightAtPos = skyLightAtInputPos[i];
skyLightAtInputPos[i] = (byte) Math.min(SKYLIGHT_COVERED, skyLightAtPos);
// if the adjacent is a different detail level, we want to render adjacent opaque
// faces to try and reduce the chance of holes on detail level borders
boolean adjacentCoversThis =
// if the adjacent is the same detail level, no special handling is necessary
!adjacentIsSameDetailLevel
// if the adjacent face is underground we probably don't need it
&& RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY
// check if this face is on a border
&&
(
(x == 0 && direction == EDhDirection.WEST)
|| (z == 0 && direction == EDhDirection.NORTH)
// TODO why does 256 represent a border? aren't LODs only 64 datapoints wide?
|| (x == 256 && direction == EDhDirection.EAST)
|| (z == 256 && direction == EDhDirection.SOUTH)
);
byte newSkyLightAtPos = adjacentCoversThis ? adjSkyLight : SKYLIGHT_COVERED;
skyLightAtInputPos[i] = (byte) Math.min(newSkyLightAtPos, skyLightAtPos);
}
}
else
@@ -33,9 +33,10 @@ import com.seibel.distanthorizons.core.util.objects.StatsMap;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.Logger;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.concurrent.*;
/**
@@ -100,7 +101,7 @@ public class ColumnRenderBuffer implements AutoCloseable
{
try
{
this.uploadBuffersUsingUploadMethod(builder, gpuUploadMethod);
this.uploadBuffers(builder, gpuUploadMethod);
uploadFuture.complete(null);
}
catch (InterruptedException e)
@@ -126,72 +127,46 @@ public class ColumnRenderBuffer implements AutoCloseable
//LOGGER.warn("Error uploading builder ["+builder+"] synchronously. Error: "+e.getMessage(), e);
}
}
private void uploadBuffersUsingUploadMethod(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException
private void uploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException
{
if (gpuUploadMethod.useEarlyMapping)
{
this.uploadBuffersMapped(builder, gpuUploadMethod);
}
else
{
this.uploadBuffersDirect(builder, gpuUploadMethod);
}
// uploading mapped buffers used to be done here,
// however due to a memory leak and complication with the previous code,
// now we only allow direct uploading.
// (There's also insufficient data to state whether mapped buffers are necessary
// for DH to upload without stuttering the main thread)
this.vbos = makeAndUploadBuffers(builder, method, this.vbos, builder.makeOpaqueVertexBuffers());
this.vbosTransparent = makeAndUploadBuffers(builder, method, this.vbosTransparent, builder.makeTransparentVertexBuffers());
this.buffersUploaded = true;
}
private void uploadBuffersMapped(LodQuadBuilder builder, EDhApiGpuUploadMethod method)
/** This resizes and returns the vbo array if necessary based on the amount of data needed for this area. */
private static GLVertexBuffer[] makeAndUploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method, GLVertexBuffer[] vbos, ArrayList<ByteBuffer> buffers) throws InterruptedException
{
// opaque vbos //
this.vbos = resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
for (int i = 0; i < this.vbos.length; i++)
try
{
if (this.vbos[i] == null)
vbos = resizeBuffer(vbos, buffers.size());
uploadBuffersDirect(vbos, buffers, method);
}
finally
{
// all the buffers must be manually freed to prevent memory leaks
if (buffers != null)
{
this.vbos[i] = new GLVertexBuffer(method.useBufferStorage);
for (ByteBuffer buffer : buffers)
{
MemoryUtil.memFree(buffer);
}
}
}
LodQuadBuilder.BufferFiller func = builder.makeOpaqueBufferFiller(method);
for (GLVertexBuffer vbo : this.vbos)
{
func.fill(vbo);
}
// transparent vbos //
this.vbosTransparent = resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
for (int i = 0; i < this.vbosTransparent.length; i++)
{
if (this.vbosTransparent[i] == null)
{
this.vbosTransparent[i] = new GLVertexBuffer(method.useBufferStorage);
}
}
LodQuadBuilder.BufferFiller transparentFillerFunc = builder.makeTransparentBufferFiller(method);
for (GLVertexBuffer vbo : this.vbosTransparent)
{
transparentFillerFunc.fill(vbo);
}
// return the array in case it was resized
return vbos;
}
private void uploadBuffersDirect(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, ArrayList<ByteBuffer> byteBuffers, EDhApiGpuUploadMethod method) throws InterruptedException
{
this.vbos = resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount());
uploadBuffersDirect(this.vbos, builder.makeOpaqueVertexBuffers(), method);
this.vbosTransparent = resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount());
uploadBuffersDirect(this.vbosTransparent, builder.makeTransparentVertexBuffers(), method);
}
private static void uploadBuffersDirect(GLVertexBuffer[] vbos, Iterator<ByteBuffer> iter, EDhApiGpuUploadMethod method) throws InterruptedException
{
long remainingMS = 0;
long MBPerMS = Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds.get();
int vboIndex = 0;
while (iter.hasNext())
for (int i = 0; i < byteBuffers.size(); i++)
{
if (vboIndex >= vbos.length)
{
@@ -207,13 +182,13 @@ public class ColumnRenderBuffer implements AutoCloseable
GLVertexBuffer vbo = vbos[vboIndex];
ByteBuffer bb = iter.next();
int size = bb.limit() - bb.position();
ByteBuffer buffer = byteBuffers.get(i);
int size = buffer.limit() - buffer.position();
try
{
vbo.bind();
vbo.uploadBuffer(bb, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
vbo.uploadBuffer(buffer, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
}
catch (Exception e)
{
@@ -222,24 +197,6 @@ public class ColumnRenderBuffer implements AutoCloseable
LOGGER.error("Failed to upload buffer: ", e);
}
if (MBPerMS > 0)
{
// upload buffers over an extended period of time
// to hopefully prevent stuttering.
remainingMS += size * MBPerMS;
if (remainingMS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS))
{
if (remainingMS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS)
{
remainingMS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
}
Thread.sleep(remainingMS / 1000000, (int) (remainingMS % 1000000));
remainingMS = 0;
}
}
vboIndex++;
}
@@ -61,22 +61,21 @@ public class ColumnRenderBufferBuilder
// vbo building //
//==============//
/** @link adjData should be null for adjacent sections that cross detail level boundaries */
public static CompletableFuture<ColumnRenderBuffer> buildAndUploadBuffersAsync(
public static CompletableFuture<LodQuadBuilder> buildBuffersAsync(
IDhClientLevel clientLevel,
ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
ColumnRenderSource renderSource, ColumnRenderSource[] adjData, boolean[] isSameDetailLevel
)
{
ThreadPoolExecutor bufferBuilderExecutor = ThreadPoolUtil.getBufferBuilderExecutor();
ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor();
if ((bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated()) ||
(bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated()))
if (bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated())
{
// one or more of the thread pools has been shut down
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
CompletableFuture<LodQuadBuilder> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
try
{
return CompletableFuture.supplyAsync(() ->
@@ -85,7 +84,7 @@ public class ColumnRenderBufferBuilder
{
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
LodQuadBuilder builder = new LodQuadBuilder(enableTransparency, clientLevel.getClientLevelWrapper());
makeLodRenderData(builder, renderSource, adjData);
makeLodRenderData(builder, renderSource, clientLevel, adjData, isSameDetailLevel);
return builder;
}
catch (UncheckedInterruptedException e)
@@ -97,8 +96,39 @@ public class ColumnRenderBufferBuilder
LOGGER.error("LodNodeBufferBuilder was unable to build quads for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: ["+ e3.getMessage()+"].", e3);
throw e3;
}
}, bufferBuilderExecutor)
.thenApplyAsync((quadBuilder) ->
}, bufferBuilderExecutor);
}
catch (RejectedExecutionException ignore)
{
// the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
CompletableFuture<LodQuadBuilder> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
}
/** @link adjData should be null for adjacent sections that cross detail level boundaries */
public static CompletableFuture<ColumnRenderBuffer> uploadBuffersAsync(
IDhClientLevel clientLevel,
ColumnRenderSource renderSource,
LodQuadBuilder quadBuilder
)
{
// TODO put into a single future/thread so it can be easily canceled
ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor();
if (bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated())
{
// one or more of the thread pools has been shut down
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
try
{
return CompletableFuture.supplyAsync(() ->
{
try
{
@@ -128,21 +158,23 @@ public class ColumnRenderBufferBuilder
}
catch (Throwable e3)
{
LOGGER.error("LodNodeBufferBuilder was unable to upload buffer: " + e3.getMessage(), e3);
LOGGER.error("LodNodeBufferBuilder was unable to upload buffer for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: [" + e3.getMessage() + "].", e3);
throw e3;
}
}, bufferUploaderExecutor);
}
catch (RejectedExecutionException ignore)
{
// the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back
// shouldn't happen, but just in case
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
}
private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, ColumnRenderSource[] adjRegions)
private static void makeLodRenderData(
LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel,
ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel)
{
//=============//
// debug check //
@@ -332,8 +364,9 @@ public class ColumnRenderBufferBuilder
long bottomDataPoint = (i + 1) < columnRenderData.size() ? columnRenderData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA;
addLodToBuffer(
clientLevel,
data, topDataPoint, bottomDataPoint,
adjColumnViews,
adjColumnViews, isSameDetailLevel,
thisDetailLevel, relX, relZ,
quadBuilder, debugSourceFlag);
}
@@ -344,8 +377,9 @@ public class ColumnRenderBufferBuilder
quadBuilder.finalizeData();
}
private static void addLodToBuffer(
IDhClientLevel clientLevel,
long data, long topData, long bottomData,
ColumnArrayView[] adjColumnViews,
ColumnArrayView[] adjColumnViews, boolean[] isSameDetailLevel,
byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ,
LodQuadBuilder quadBuilder, ColumnRenderSource.DebugSourceFlag debugSource)
{
@@ -475,14 +509,14 @@ public class ColumnRenderBufferBuilder
}
ColumnBox.addBoxQuadsToBuilder(
quadBuilder, // buffer
width, ySize, width, // setWidth
x, yMin, z, // setOffset
color, // setColor
blockMaterialId, // irisBlockMaterialId
RenderDataPointUtil.getLightSky(data), // setSkyLights
fullBright ? 15 : RenderDataPointUtil.getLightBlock(data), // setBlockLights
topData, bottomData, adjColumnViews); // setAdjData
quadBuilder, clientLevel,
width, ySize, width,
x, yMin, z,
color,
blockMaterialId,
RenderDataPointUtil.getLightSky(data),
fullBright ? 15 : RenderDataPointUtil.getLightBlock(data),
topData, bottomData, adjColumnViews, isSameDetailLevel);
}
}
@@ -38,6 +38,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.apache.logging.log4j.Logger;
import org.lwjgl.system.MemoryUtil;
/**
* Used to create the quads before they are converted to render-able buffers. <br><br>
@@ -205,10 +206,124 @@ public class LodQuadBuilder
//=================//
// data finalizing //
//=================//
/** runs any final data cleanup, merging, etc. */
public void finalizeData() { this.mergeQuads(); }
/** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads()
{
long mergeCount = 0; // can be used for debugging
long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
if (preQuadsCount <= 1)
{
return;
}
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
}
// only run the second merge if the face is the top or bottom
if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal())
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
}
}
}
//long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
//LOGGER.trace("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads");
}
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
private static long mergeQuadsInternal(ArrayList<BufferQuad>[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection)
{
if (list[directionIndex].size() <= 1)
return 0;
list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection));
long mergeCount = 0;
ListIterator<BufferQuad> iter = list[directionIndex].listIterator();
BufferQuad currentQuad = iter.next();
while (iter.hasNext())
{
BufferQuad nextQuad = iter.next();
if (currentQuad.tryMerge(nextQuad, mergeDirection))
{
// merge successful, attempt to merge the next quad
mergeCount++;
iter.set(null);
}
else
{
// merge fail, move on to the next quad
currentQuad = nextQuad;
}
}
list[directionIndex].removeIf(Objects::isNull);
return mergeCount;
}
//==============//
// add vertices //
// buffer setup //
//==============//
public ArrayList<ByteBuffer> makeOpaqueVertexBuffers() { return this.makeVertexBuffers(this.opaqueQuads); }
public ArrayList<ByteBuffer> makeTransparentVertexBuffers() { return this.makeVertexBuffers(this.transparentQuads); }
private ArrayList<ByteBuffer> makeVertexBuffers(ArrayList<BufferQuad>[] quadList)
{
ArrayList<ByteBuffer> byteBufferList = new ArrayList<>(3);
ByteBuffer buffer = null;
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
// ignore empty directions
if (quadList[directionIndex].isEmpty())
{
continue;
}
// put all the quads in this direction into the buffer
for (int quadIndex = 0; quadIndex < quadList[directionIndex].size(); quadIndex++)
{
// if this is the first iteration or the buffer is full,
// create a new buffer
if (buffer == null || !buffer.hasRemaining())
{
buffer = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER);
byteBufferList.add(buffer);
}
this.putQuad(buffer, quadList[directionIndex].get(quadIndex));
}
}
// rewind all the buffers so they can be read from
for (int i = 0; i < byteBufferList.size(); i++)
{
buffer = byteBufferList.get(i);
buffer.limit(buffer.position());
buffer.rewind();
}
return byteBufferList;
}
private void putQuad(ByteBuffer bb, BufferQuad quad)
{
int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()];
@@ -266,10 +381,10 @@ public class LodQuadBuilder
if (quad.direction.getAxis().isHorizontal() || quad.direction == EDhDirection.DOWN)
{
if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT
// if we want the color to fade, only apply the dirt color to the bottom vertices
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
// always render the bottom as dirt
|| quad.direction == EDhDirection.DOWN)
// if we want the color to fade, only apply the dirt color to the bottom vertices
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
// always render the bottom as dirt
|| quad.direction == EDhDirection.DOWN)
{
// for horizontal and bottom faces of grass blocks, use the dirt color to
// prevent green cliff walls
@@ -277,7 +392,7 @@ public class LodQuadBuilder
color = ColorUtil.applyShade(color, MC.getShade(quad.direction));
}
}
}
}
}
}
@@ -291,7 +406,6 @@ public class LodQuadBuilder
mx, my, mz);
}
}
private void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte normalIndex, byte irisBlockMaterialId, byte skylight, byte blocklight, int mx, int my, int mz)
{
skylight %= 16;
@@ -332,389 +446,6 @@ public class LodQuadBuilder
//=================//
// data finalizing //
//=================//
/** runs any final data cleanup, merging, etc. */
public void finalizeData() { this.mergeQuads(); }
/** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads()
{
long mergeCount = 0;
long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
if (preQuadsCount <= 1)
{
return;
}
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest);
}
// only run the second merge if the face is the top or bottom
if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal())
{
mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
if (this.doTransparency)
{
mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
}
}
}
long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount();
LOGGER.debug("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads");
}
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
private static long mergeQuadsInternal(ArrayList<BufferQuad>[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection)
{
if (list[directionIndex].size() <= 1)
return 0;
list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection));
long mergeCount = 0;
ListIterator<BufferQuad> iter = list[directionIndex].listIterator();
BufferQuad currentQuad = iter.next();
while (iter.hasNext())
{
BufferQuad nextQuad = iter.next();
if (currentQuad.tryMerge(nextQuad, mergeDirection))
{
// merge successful, attempt to merge the next quad
mergeCount++;
iter.set(null);
}
else
{
// merge fail, move on to the next quad
currentQuad = nextQuad;
}
}
list[directionIndex].removeIf(Objects::isNull);
return mergeCount;
}
//==============//
// buffer setup //
//==============//
public Iterator<ByteBuffer> makeOpaqueVertexBuffers()
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER)
.order(ByteOrder.nativeOrder());
int dir = skipEmpty(0);
int quad = 0;
private int skipEmpty(int d)
{
while (d < 6 && opaqueQuads[d].isEmpty())
{
d++;
}
return d;
}
@Override
public boolean hasNext()
{
return dir < 6;
}
@Override
public ByteBuffer next()
{
if (dir >= 6)
{
return null;
}
bb.clear();
bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER);
while (bb.hasRemaining() && dir < 6)
{
writeData();
}
bb.limit(bb.position());
bb.rewind();
return bb;
}
private void writeData()
{
int i = quad;
for (; i < opaqueQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, opaqueQuads[dir].get(i));
}
if (i >= opaqueQuads[dir].size())
{
quad = 0;
dir++;
dir = skipEmpty(dir);
}
else
{
quad = i;
}
}
};
}
public Iterator<ByteBuffer> makeTransparentVertexBuffers()
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER)
.order(ByteOrder.nativeOrder());
int directionIndex = this.skipEmptyDirectionIndices(0);
int quad = 0;
private int skipEmptyDirectionIndices(int directionIndex)
{
while (directionIndex < 6 &&
(LodQuadBuilder.this.transparentQuads[directionIndex] == null
|| LodQuadBuilder.this.transparentQuads[directionIndex].isEmpty()))
{
directionIndex++;
}
return directionIndex;
}
@Override
public boolean hasNext() { return this.directionIndex < 6; }
@Override
public ByteBuffer next()
{
if (this.directionIndex >= 6)
{
return null;
}
this.bb.clear();
this.bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER);
while (this.bb.hasRemaining() && this.directionIndex < 6)
{
this.writeData();
}
this.bb.limit(this.bb.position());
this.bb.rewind();
return this.bb;
}
private void writeData()
{
int i = this.quad;
for (; i < LodQuadBuilder.this.transparentQuads[this.directionIndex].size(); i++)
{
if (!this.bb.hasRemaining())
{
break;
}
putQuad(this.bb, LodQuadBuilder.this.transparentQuads[this.directionIndex].get(i));
}
if (i >= LodQuadBuilder.this.transparentQuads[this.directionIndex].size())
{
this.quad = 0;
this.directionIndex++;
this.directionIndex = this.skipEmptyDirectionIndices(this.directionIndex);
}
else
{
this.quad = i;
}
}
};
}
public interface BufferFiller
{
/** If true: more data needs to be filled */
boolean fill(GLVertexBuffer vbo);
}
public BufferFiller makeOpaqueBufferFiller(EDhApiGpuUploadMethod method)
{
return new BufferFiller()
{
int dir = 0;
int quad = 0;
public boolean fill(GLVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.setVertexCount(0);
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method,
ColumnRenderBuffer.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer();
vbo.setVertexCount(numOfQuads * 4);
return dir < 6;
}
private int _countRemainingQuads()
{
int a = opaqueQuads[dir].size() - quad;
for (int i = dir + 1; i < opaqueQuads.length; i++)
{
a += opaqueQuads[i].size();
}
return a;
}
private void writeData(ByteBuffer bb)
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < opaqueQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, opaqueQuads[dir].get(i));
}
if (i >= opaqueQuads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && opaqueQuads[dir].isEmpty())
{
dir++;
}
}
else
{
quad = i;
}
}
};
}
public BufferFiller makeTransparentBufferFiller(EDhApiGpuUploadMethod method)
{
return new BufferFiller()
{
int dir = 0;
int quad = 0;
public boolean fill(GLVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.setVertexCount(0);
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method,
ColumnRenderBuffer.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer();
vbo.setVertexCount(numOfQuads * 4);
return dir < 6;
}
private int _countRemainingQuads()
{
int a = transparentQuads[dir].size() - quad;
for (int i = dir + 1; i < transparentQuads.length; i++)
{
a += transparentQuads[i].size();
}
return a;
}
private void writeData(ByteBuffer bb)
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < transparentQuads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, transparentQuads[dir].get(i));
}
if (i >= transparentQuads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && transparentQuads[dir].isEmpty())
{
dir++;
}
}
else
{
quad = i;
}
}
};
}
//=========//
// getters //
//=========//
@@ -1,251 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.core.dataObjects.transformers;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.LogManager;
public class ChunkToLodBuilder implements AutoCloseable
{
public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), () -> Config.Client.Advanced.Logging.logLodBuilderEvent.get());
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static final long MAX_TICK_TIME_NS = 1000000000L / 20L;
private final ConcurrentHashMap<DhChunkPos, IChunkWrapper> concurrentChunkToBuildByChunkPos = new ConcurrentHashMap<>();
private final ConcurrentLinkedDeque<Task> concurrentTaskToBuildList = new ConcurrentLinkedDeque<>();
private final AtomicInteger runningCount = new AtomicInteger(0);
//==============//
// constructors //
//==============//
public ChunkToLodBuilder() { }
//=================//
// data generation //
//=================//
public CompletableFuture<FullDataSourceV2> tryGenerateData(IChunkWrapper chunkWrapper)
{
if (chunkWrapper == null)
{
throw new NullPointerException("ChunkWrapper cannot be null!");
}
IChunkWrapper oldChunk = this.concurrentChunkToBuildByChunkPos.put(chunkWrapper.getChunkPos(), chunkWrapper); // an Exchange operation
// If there's old chunk, that means we just replaced an unprocessed old request on generating data on this pos.
// if so, we can just return null to signal this, as the old request's future will instead be the proper one
// that will return the latest generated data.
if (oldChunk != null)
{
return null;
}
// Otherwise, it means we're the first to do so. Let's submit our task to this entry.
CompletableFuture<FullDataSourceV2> future = new CompletableFuture<>();
this.concurrentTaskToBuildList.addLast(new Task(chunkWrapper.getChunkPos(), future));
return future;
}
// TODO why on tick?
public void tick()
{
int threadCount = ThreadPoolUtil.getWorkerThreadCount();
if (this.runningCount.get() >= threadCount)
{
return;
}
else if (this.concurrentTaskToBuildList.isEmpty())
{
return;
}
else if (MC != null && !MC.playerExists())
{
// MC hasn't finished loading (or is currently unloaded)
// can be uncommented if tasks aren't being cleared correctly
//this.clearCurrentTasks();
return;
}
ThreadPoolExecutor lodBuilderExecutor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (lodBuilderExecutor == null)
{
return;
}
for (int i = 0; i < threadCount; i++)
{
this.runningCount.incrementAndGet();
try
{
CompletableFuture.runAsync(() ->
{
try
{
this.tickThreadTask();
}
finally
{
this.runningCount.decrementAndGet();
}
}, lodBuilderExecutor);
}
catch (RejectedExecutionException ignore) { /* the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back */ }
}
}
private void tickThreadTask()
{
long time = System.nanoTime();
int count = 0;
boolean allDone = false;
while (true)
{
// run until we either run out of time, or all tasks are complete
if (System.nanoTime() - time > MAX_TICK_TIME_NS && !this.concurrentTaskToBuildList.isEmpty())
{
break;
}
Task task = this.concurrentTaskToBuildList.pollFirst();
if (task == null)
{
allDone = true;
break;
}
count++;
IChunkWrapper latestChunk = this.concurrentChunkToBuildByChunkPos.remove(task.chunkPos); // Basically an Exchange operation
if (latestChunk == null)
{
LOGGER.error("Somehow Task at " + task.chunkPos + " has latestChunk as null. Skipping task.");
task.future.complete(null);
continue;
}
try
{
if (LodDataBuilder.canGenerateLodFromChunk(latestChunk))
{
FullDataSourceV2 dataSource = LodDataBuilder.createGeneratedDataSource(latestChunk);
if (dataSource != null)
{
task.future.complete(dataSource);
continue;
}
}
else if (task.generationAttemptExpirationTimeMs < System.currentTimeMillis())
{
// this task won't be re-queued
//LOGGER.trace("removed chunk "+task.chunkPos);
continue;
}
}
catch (Exception ex)
{
LOGGER.error("Error while processing Task at " + task.chunkPos, ex);
}
// Failed to build due to chunk not meeting requirement,
// re-add it to the queue so it can be tested next time
IChunkWrapper casChunk = this.concurrentChunkToBuildByChunkPos.putIfAbsent(task.chunkPos, latestChunk); // CAS operation with expected=null
if (casChunk == null || latestChunk.isStillValid()) // That means CAS have been successful
{
this.concurrentTaskToBuildList.addLast(task); // Then add back the same old task.
}
else // Else, it means someone managed to sneak in a new gen request in this pos. Then lets drop this old task.
{
task.future.complete(null);
}
count--;
}
long time2 = System.nanoTime();
if (!allDone)
{
//LOGGER.info("Completed {} tasks in {} in this tick", count, Duration.ofNanos(time2 - time));
}
else if (count > 0)
{
//LOGGER.info("Completed all {} tasks in {}", count, Duration.ofNanos(time2 - time));
}
}
/**
* should be called whenever changing levels/worlds
* to prevent trying to generate LODs for chunk(s) that are no longer loaded
* (which can cause exceptions)
*/
public void clearCurrentTasks()
{
this.concurrentTaskToBuildList.clear();
this.concurrentChunkToBuildByChunkPos.clear();
}
//==============//
// base methods //
//==============//
@Override
public void close() { this.clearCurrentTasks(); }
//================//
// helper classes //
//================//
private static class Task
{
public final DhChunkPos chunkPos;
public final CompletableFuture<FullDataSourceV2> future;
/** This is tracked so impossible tasks can be removed from the queue */
public long generationAttemptExpirationTimeMs = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10);
Task(DhChunkPos chunkPos, CompletableFuture<FullDataSourceV2> future)
{
this.chunkPos = chunkPos;
this.future = future;
}
}
}
@@ -178,6 +178,7 @@ public class FullDataToRenderDataTransformer
HashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(level.getLevelWrapper());
HashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(level.getLevelWrapper());
int caveCullingMaxY = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get() - level.getMinY();
boolean caveCullingEnabled =
Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get()
&& (
@@ -196,7 +197,7 @@ public class FullDataToRenderDataTransformer
int skylightToApplyToNextBlock = -1;
int blocklightToApplyToNextBlock = -1;
int columnOffset = 0;
int renderDataIndex = 0;
@@ -205,12 +206,13 @@ public class FullDataToRenderDataTransformer
//==================================//
// goes from the top down
for (int i = 0; i < fullColumnData.size(); i++)
for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++)
{
long fullData = fullColumnData.getLong(i);
long fullData = fullColumnData.getLong(fullDataIndex);
int bottomY = FullDataPointUtil.getBottomY(fullData);
int blockHeight = FullDataPointUtil.getHeight(fullData);
int topY = bottomY + blockHeight;
int id = FullDataPointUtil.getId(fullData);
int blockLight = FullDataPointUtil.getBlockLight(fullData);
int skyLight = FullDataPointUtil.getSkyLight(fullData);
@@ -252,24 +254,26 @@ public class FullDataToRenderDataTransformer
{
if (caveCullingEnabled
// assume this data point is underground if it has no sky-light
&& skyLight == LodUtil.MIN_MC_LIGHT
&& skyLight == LodUtil.MIN_MC_LIGHT
// ignore caves above a certain height to prevent floating islands from having walls underneath them
&& topY < caveCullingMaxY
// cave culling shouldn't happen when at the top of the world
&& columnOffset != 0
&& renderDataIndex != 0 && fullDataIndex != 0
// cave culling can't happen when at the bottom of the world
&& columnOffset != fullColumnData.size())
&& (fullDataIndex+1) < fullColumnData.size())
{
// we need to get the next sky/block lights because
// the air block here will always have a light of 0/0 due to only the top of the LOD's light being saved.
long nextFullData = fullColumnData.getLong(i+1);
long nextFullData = fullColumnData.getLong(fullDataIndex+1);
int nextSkyLight = FullDataPointUtil.getSkyLight(nextFullData);
if (nextSkyLight == LodUtil.MIN_MC_LIGHT
&& ColorUtil.getAlpha(lastColor) == 255)
{
// replace the previous block with new bottom
long columnData = renderColumnData.get(columnOffset - 1);
long columnData = renderColumnData.get(renderDataIndex - 1);
columnData = RenderDataPointUtil.setYMin(columnData, bottomY);
renderColumnData.set(columnOffset - 1, columnData);
renderColumnData.set(renderDataIndex - 1, columnData);
}
continue;
@@ -337,20 +341,20 @@ public class FullDataToRenderDataTransformer
//=============================//
// check if they share a top-bottom face and if they have same color
if (color == lastColor && bottomY + blockHeight == lastBottom && columnOffset > 0)
if (color == lastColor && bottomY + blockHeight == lastBottom && renderDataIndex > 0)
{
//replace the previous block with new bottom
long columnData = renderColumnData.get(columnOffset - 1);
long columnData = renderColumnData.get(renderDataIndex - 1);
columnData = RenderDataPointUtil.setYMin(columnData, bottomY);
renderColumnData.set(columnOffset - 1, columnData);
renderColumnData.set(renderDataIndex - 1, columnData);
}
else
{
// add the block
isColumnVoid = false;
long columnData = RenderDataPointUtil.createDataPoint(bottomY + blockHeight, bottomY, color, skyLight, blockLight, block.getMaterialId());
renderColumnData.set(columnOffset, columnData);
columnOffset++;
renderColumnData.set(renderDataIndex, columnData);
renderDataIndex++;
}
lastBottom = bottomY;
lastColor = color;
@@ -320,6 +320,8 @@ public class LodDataBuilder
// AND the below loop won't run.
int size = (columnDataPoints != null) ? columnDataPoints.size() : 0;
// TODO make missing air LODs
// TODO merge duplicate datapoints
LongArrayList packedDataPoints = new LongArrayList(new long[size]);
for (int index = 0; index < size; index++)
{
@@ -26,12 +26,12 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.api.enums.config.EDhApiServerFolderNameMode;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.util.objects.ParsedIp;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import java.io.File;
import java.util.*;
@@ -138,7 +138,7 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
{
// use the first existing sub-dimension
String folderName = folders.get(0).getName();
LOGGER.info("Default Sub Dimension set to: [" + LodUtil.shortenString(folderName, 8) + "...]");
LOGGER.info("Default Sub Dimension set to: [" + StringUtil.shortenString(folderName, 8) + "...]");
return folders.get(0);
}
else
@@ -40,6 +40,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.LogManager;
@@ -92,7 +93,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
if (potentialLevelFolders.size() == 0)
{
String newId = UUID.randomUUID().toString();
LOGGER.info("No potential level files found. Creating a new sub dimension with the ID ["+LodUtil.shortenString(newId, 8)+"]...");
LOGGER.info("No potential level files found. Creating a new sub dimension with the ID ["+ StringUtil.shortenString(newId, 8)+"]...");
this.foundLevelFile = this.CreateSubDimFolder(newId);
}
}
@@ -207,7 +208,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
SubDimCompare mostSimilarSubDim = null;
for (File testLevelFolder : this.potentialLevelFolders)
{
LOGGER.info("Testing level folder: [" + LodUtil.shortenString(testLevelFolder.getName(), 8) + "]");
LOGGER.info("Testing level folder: [" + StringUtil.shortenString(testLevelFolder.getName(), 8) + "]");
FullDataSourceV2 testFullDataSource = null;
try
@@ -328,8 +329,8 @@ public class SubDimensionLevelMatcher implements AutoCloseable
}
String subDimShortName = LodUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging
String equalPercent = LodUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5);
String subDimShortName = StringUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging
String equalPercent = StringUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5);
LOGGER.info("Sub dimension ["+subDimShortName+"...] is current dimension probability: "+equalPercent+" ("+equalDataPoints+"/"+totalDataPointCount+")");
}
catch (Exception e)
@@ -359,7 +360,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
{
// we found a sub dim folder that is similar, use it
LOGGER.info("Sub Dimension set to: [" + LodUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]");
LOGGER.info("Sub Dimension set to: [" + StringUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]");
return mostSimilarSubDim.folder;
}
else
@@ -369,7 +370,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
String newId = UUID.randomUUID().toString();
double highestEqualityPercent = mostSimilarSubDim != null ? mostSimilarSubDim.getPercentEqual() : 0;
String message = "No suitable sub dimension found. The highest equality was [" + LodUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + LodUtil.shortenString(newId, 8) + "...";
String message = "No suitable sub dimension found. The highest equality was [" + StringUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + StringUtil.shortenString(newId, 8) + "...";
LOGGER.info(message);
File folder = this.CreateSubDimFolder(newId);
@@ -147,7 +147,7 @@ public class DhLightingEngine
{
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
// set each pos' sky light all the way down until a opaque block is hit
// set each pos' sky light all the way down until an opaque block is hit
for (int y = maxY; y >= minY; y--)
{
IBlockStateWrapper block = chunk.getBlockState(relX, y, relZ);
@@ -19,28 +19,19 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkModifiedEvent;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.transformers.ChunkToLodBuilder;
import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.CloudRenderHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.sql.repo.ChunkHashRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.apache.logging.log4j.Logger;
@@ -59,8 +50,6 @@ public abstract class AbstractDhLevel implements IDhLevel
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public final ChunkToLodBuilder chunkToLodBuilder;
/** if this is null then the other handler is probably null too, but just in case */
@Nullable
public ChunkHashRepo chunkHashRepo;
@@ -71,6 +60,7 @@ public abstract class AbstractDhLevel implements IDhLevel
protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 2_000);
/** contains the {@link DhChunkPos} for each {@link DhSectionPos} that are queued to save via {@link AbstractDhLevel#delayedFullDataSourceSaveCache} */
protected final ConcurrentHashMap<Long, HashSet<DhChunkPos>> updatedChunkPosSetBySectionPos = new ConcurrentHashMap<>();
protected final ConcurrentHashMap<DhChunkPos, Integer> updatedChunkHashesByChunkPos = new ConcurrentHashMap<>();
/** Will be null if clouds shouldn't be rendered for this level. */
@Nullable
@@ -83,10 +73,7 @@ public abstract class AbstractDhLevel implements IDhLevel
// constructor //
//=============//
protected AbstractDhLevel()
{
this.chunkToLodBuilder = new ChunkToLodBuilder();
}
protected AbstractDhLevel() { }
/**
* Creating the repos requires access to the level file, which isn't
@@ -152,7 +139,7 @@ public abstract class AbstractDhLevel implements IDhLevel
public int getUnsavedDataSourceCount() { return this.delayedFullDataSourceSaveCache.getUnsavedCount(); }
@Override
public void updateChunkAsync(IChunkWrapper chunkWrapper)
public void updateChunkAsync(IChunkWrapper chunkWrapper, int chunkHash)
{
FullDataSourceV2 dataSource = FullDataSourceV2.createFromChunk(chunkWrapper);
if (dataSource == null)
@@ -171,6 +158,7 @@ public abstract class AbstractDhLevel implements IDhLevel
chunkPosSet.add(chunkWrapper.getChunkPos());
return chunkPosSet;
});
this.updatedChunkHashesByChunkPos.put(chunkWrapper.getChunkPos(), chunkHash);
// batch updates to reduce overhead when flying around or breaking/placing a lot of blocks in an area
this.delayedFullDataSourceSaveCache.queueDataSourceForUpdateAndSave(dataSource);
@@ -185,6 +173,13 @@ public abstract class AbstractDhLevel implements IDhLevel
{
for (DhChunkPos chunkPos : updatedChunkPosSet)
{
// save after the data source has been updated to prevent saving the hash without the associated datasource
Integer chunkHash = this.updatedChunkHashesByChunkPos.remove(chunkPos);
if (this.chunkHashRepo != null && chunkHash != null)
{
this.chunkHashRepo.save(new ChunkHashDTO(chunkPos, chunkHash));
}
ApiEventInjector.INSTANCE.fireAllEvents(
DhApiChunkModifiedEvent.class,
new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.x, chunkPos.z));
@@ -212,14 +207,6 @@ public abstract class AbstractDhLevel implements IDhLevel
ChunkHashDTO dto = this.chunkHashRepo.getByKey(pos);
return (dto != null) ? dto.chunkHash : 0;
}
@Override
public void setChunkHash(DhChunkPos pos, int chunkHash)
{
if (this.chunkHashRepo != null)
{
this.chunkHashRepo.save(new ChunkHashDTO(pos, chunkHash));
}
}
@@ -262,8 +249,6 @@ public abstract class AbstractDhLevel implements IDhLevel
@Override
public void close()
{
this.chunkToLodBuilder.close();
if (this.chunkHashRepo != null)
{
this.chunkHashRepo.close();
@@ -164,7 +164,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
{
try
{
this.chunkToLodBuilder.tick();
this.clientside.clientTick();
if (this.dataRefreshQueue != null)
@@ -95,7 +95,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
{ this.clientside.renderDeferred(renderEventParam, profiler); }
@Override
public void serverTick() { this.chunkToLodBuilder.tick(); }
public void serverTick() { }
@Override
public void doWorldGen()
@@ -265,8 +265,6 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
@Override
public void serverTick()
{
this.chunkToLodBuilder.tick();
// Send finished data source requests
for (Map.Entry<Long, DataSourceRequestGroup> entry : this.requestGroupsByPos.entrySet())
{
@@ -45,8 +45,7 @@ public interface IDhLevel extends AutoCloseable
/** @return 0 if no hash is known */
int getChunkHash(DhChunkPos pos);
void setChunkHash(DhChunkPos pos, int chunkHash);
void updateChunkAsync(IChunkWrapper chunk);
void updateChunkAsync(IChunkWrapper chunk, int newChunkHash);
void loadBeaconBeamsInPos(long pos);
void setBeaconBeamsForChunk(DhChunkPos chunkPos, List<BeaconBeamDTO> beamList);
@@ -73,6 +73,8 @@ public class F3Screen
ThreadPoolExecutor worldGenPool = ThreadPoolUtil.getWorldGenExecutor();
ThreadPoolExecutor fileHandlerPool = ThreadPoolUtil.getFileHandlerExecutor();
ThreadPoolExecutor updatePool = ThreadPoolUtil.getUpdatePropagatorExecutor();
ThreadPoolExecutor bufferBuilderPool = ThreadPoolUtil.getBufferBuilderExecutor();
ThreadPoolExecutor bufferUploaderPool = ThreadPoolUtil.getBufferUploaderExecutor();
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
Iterable<? extends IDhLevel> levelIterator = world.getAllLoadedLevels();
@@ -89,6 +91,8 @@ public class F3Screen
messageList.add(getThreadPoolStatString("World Gen", worldGenPool));//"World Gen Tasks: 40/5304, (in progress: 7)");
messageList.add(getThreadPoolStatString("File Handler", fileHandlerPool));
messageList.add(getThreadPoolStatString("Update Propagator", updatePool));
messageList.add(getThreadPoolStatString("Buffer Builder", bufferBuilderPool));
messageList.add(getThreadPoolStatString("Buffer Uploader", bufferUploaderPool));
messageList.add("");
// chunk updates
messageList.add(SharedApi.INSTANCE.getDebugMenuString());
@@ -34,11 +34,11 @@ import java.util.function.LongConsumer;
* For the specifics of how they compare can be viewed in the constants {@link #SECTION_BLOCK_DETAIL_LEVEL},
* {@link #SECTION_CHUNK_DETAIL_LEVEL}, and {@link #SECTION_REGION_DETAIL_LEVEL}).<br><br>
*
* <strong>Why does the smallest render section represent 2x2 MC chunks (section detail level 6)? </strong> <br>
* <strong>Why does the smallest render section represent 4x4 MC chunks (section detail level 6)? </strong> <br>
* A section defines what unit the quad tree works in, because of that we don't want that unit to be too big or too small. <br>
* <strong>Too small</strong>, and we'll have 1,000s of sections running around, all needing individual files and render buffers.<br>
* <strong>Too big</strong>, and the LOD dropoff will be very noticeable.<br>
* With those thoughts in mind we decided on a smallest section size of 32 data points square (IE 2x2 chunks).
* With those thoughts in mind we decided on a smallest section size of 64 data points square (IE 4x4 chunks).
*
* @author Leetom
*/
@@ -54,10 +54,6 @@ public class DhFrustumBounds implements IDhApiCullingFrustum
Vector3f lodMin = new Vector3f(lodBlockPosMinX, this.worldMinY, lodBlockPosMinZ);
Vector3f lodMax = new Vector3f(lodBlockPosMinX + lodBlockWidth, this.worldMaxY, lodBlockPosMinZ + lodBlockWidth);
if (lodMax.x < this.boundsMin.x || lodMin.x > this.boundsMax.x) return false;
if (lodMax.z < this.boundsMin.z || lodMin.z > this.boundsMax.z) return false;
if (this.worldMaxY < this.boundsMin.y || this.worldMinY > this.boundsMax.y) return false;
return this.frustum.testAab(lodMin, lodMax);
}
@@ -189,7 +189,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
try
{
LodRenderSection renderSection = this.getValue(pos);
if (renderSection != null && renderSection.renderingEnabled)
if (renderSection != null && renderSection.getRenderingEnabled())
{
renderSection.uploadRenderDataToGpuAsync();
}
@@ -296,7 +296,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
if (DhSectionPos.getDetailLevel(sectionPos) > expectedDetailLevel)
{
// section detail level too high //
boolean thisPosIsRendering = renderSection.renderingEnabled;
boolean thisPosIsRendering = renderSection.getRenderingEnabled();
boolean allChildrenSectionsAreLoaded = true;
// recursively update all child render sections
@@ -314,24 +314,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}
else
{
if (renderSection.renderingEnabled
&& Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get())
{
// show that this position has just been disabled
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(renderSection.pos, 128f, 156f, 0.09f, Color.CYAN.darker()),
0.2, 32f
)
);
}
// all child positions are loaded, disable this section and enable its children.
if (renderSection.renderingEnabled)
{
this.level.unloadBeaconBeamsInPos(renderSection.pos);
}
renderSection.renderingEnabled = false;
renderSection.setRenderingEnabled(false);
// walk back down the tree and enable the child sections //TODO there are probably more efficient ways of doing this, but this will work for now
for (int i = 0; i < 4; i++)
@@ -382,17 +366,14 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
if (!parentSectionIsRendering && renderSection.canRender())
{
// if rendering is already enabled we don't have to re-enable it
if (!renderSection.renderingEnabled)
if (!renderSection.getRenderingEnabled())
{
renderSection.renderingEnabled = true;
this.level.loadBeaconBeamsInPos(renderSection.pos);
// delete/disable children, all of them will be a lower detail level than requested
quadNode.deleteAllChildren((childRenderSection) ->
{
if (childRenderSection != null)
{
if (childRenderSection.renderingEnabled)
if (childRenderSection.getRenderingEnabled())
{
// show that this position's rendering has been disabled due to a parent rendering
DebugRenderer.makeParticle(
@@ -403,10 +384,12 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
);
}
childRenderSection.renderingEnabled = false;
childRenderSection.setRenderingEnabled(false);
childRenderSection.close();
}
});
renderSection.setRenderingEnabled(true);
}
}
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBufferBuilder;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodQuadBuilder;
import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
@@ -69,7 +70,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
private final LodQuadTree quadTree;
public boolean renderingEnabled = false;
private boolean renderingEnabled = false;
private boolean canRender = false;
/** this reference is necessary so we can determine what VBO to render */
@@ -80,7 +81,19 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
* Encapsulates everything between pulling data from the database (including neighbors)
* up to the point when geometry data is uploaded to the GPU.
*/
private CompletableFuture<Void> uploadRenderDataToGpuFuture = null;
private CompletableFuture<Void> buildAndUploadRenderDataToGpuFuture = null;
/**
* Represents just building the {@link LodQuadBuilder}. <br>
* Separate from {@link LodRenderSection#bufferUploadFuture} because they run on
* different thread pools and need to be canceled separately.
*/
private CompletableFuture<LodQuadBuilder> bufferBuildFuture = null;
/**
* Represents just uploading the {@link LodQuadBuilder} to the GPU. <br>
* Separate from {@link LodRenderSection#bufferBuildFuture} because they run on
* different thread pools and need to be canceled separately.
*/
private CompletableFuture<ColumnRenderBuffer> bufferUploadFuture = null;
private final ReentrantLock getRenderSourceLock = new ReentrantLock();
/** Stored as a class variable so we can reuse it's result across multiple LOD loads if necessary */
@@ -114,6 +127,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
// render data loading/uploading //
//===============================//
// TODO cleanup, there's a lot of nested futures and duplicate error handling here and it's hard to read
public synchronized void uploadRenderDataToGpuAsync()
{
if (!GLProxy.hasInstance())
@@ -123,7 +137,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
return;
}
if (this.uploadRenderDataToGpuFuture != null)
if (this.buildAndUploadRenderDataToGpuFuture != null)
{
// don't accidentally queue multiple uploads at the same time
return;
@@ -137,7 +151,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
return;
}
this.uploadRenderDataToGpuFuture = CompletableFuture.runAsync(() ->
this.buildAndUploadRenderDataToGpuFuture = CompletableFuture.runAsync(() ->
{
//==================//
// load render data //
@@ -145,15 +159,15 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.tryDecrementingLoadFutureArray(this.adjacentLoadRefFutures);
ReferencedFutureWrapper thisLoadFuture = this.getRenderSourceAsync();
ReferencedFutureWrapper[] adjLoadRefFutures = this.getNeighborRenderSourcesAsync();
ReferencedFutureWrapper thisRenderSourceLoadFuture = this.getRenderSourceAsync();
ReferencedFutureWrapper[] adjRenderSourceLoadRefFutures = this.getNeighborRenderSourcesAsync();
// wait for all futures to complete together,
// merging the futures makes loading significantly faster than loading this position then loading its neighbors
ArrayList<CompletableFuture<ColumnRenderSource>> futureList = new ArrayList<>();
futureList.add(thisLoadFuture.future);
for (ReferencedFutureWrapper refFuture : adjLoadRefFutures)
futureList.add(thisRenderSourceLoadFuture.future);
for (ReferencedFutureWrapper refFuture : adjRenderSourceLoadRefFutures)
{
futureList.add(refFuture.future);
}
@@ -162,73 +176,107 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{
try
{
ColumnRenderSource renderSource = thisLoadFuture.future.get();
ColumnRenderSource renderSource = thisRenderSourceLoadFuture.future.get();
if (renderSource == null || renderSource.isEmpty())
{
thisLoadFuture.decrementRefCount();
for (ReferencedFutureWrapper futureWrapper : adjLoadRefFutures)
thisRenderSourceLoadFuture.decrementRefCount();
for (ReferencedFutureWrapper futureWrapper : adjRenderSourceLoadRefFutures)
{
futureWrapper.decrementRefCount();
}
// nothing needs to be rendered
this.canRender = false;
this.uploadRenderDataToGpuFuture = null;
this.buildAndUploadRenderDataToGpuFuture = null;
this.bufferBuildFuture = null;
return;
}
//==============================//
// build/upload new render data //
//==============================//
//=======================//
// build new render data //
//=======================//
try
{
ColumnRenderBuffer previousBuffer = this.renderBuffer;
ColumnRenderSource[] adjacentRenderSections = new ColumnRenderSource[EDhDirection.ADJ_DIRECTIONS.length];
boolean[] adjIsSameDetailLevel = new boolean[EDhDirection.ADJ_DIRECTIONS.length];
for (int i = 0; i < EDhDirection.ADJ_DIRECTIONS.length; i++)
{
adjacentRenderSections[i] = adjLoadRefFutures[i].future.getNow(null);
adjacentRenderSections[i] = adjRenderSourceLoadRefFutures[i].future.getNow(null);
// if the adjacent position isn't the same detail level the buffer building logic
// will need to be slightly different in order to reduce holes in the LODs
EDhDirection direction = EDhDirection.ADJ_DIRECTIONS[i];
adjIsSameDetailLevel[direction.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(direction);
}
ColumnRenderBufferBuilder.buildAndUploadBuffersAsync(this.level, renderSource, adjacentRenderSections)
.thenAccept((buffer) ->
if (this.bufferBuildFuture != null)
{
// shouldn't normally happen, but just in case canceling the previous future
// prevents the CPU from working on something that won't be used
this.bufferBuildFuture.cancel(true);
}
this.bufferBuildFuture = ColumnRenderBufferBuilder.buildBuffersAsync(this.level, renderSource, adjacentRenderSections, adjIsSameDetailLevel);
this.bufferBuildFuture.thenAccept((lodQuadBuilder) ->
{
// upload complete, clean up the old data if
this.renderBuffer = buffer;
this.canRender = (buffer != null);
this.uploadRenderDataToGpuFuture = null;
if (previousBuffer != null)
//===================================//
// upload new render data to the GPU //
//===================================//
if (this.bufferUploadFuture != null)
{
previousBuffer.close();
// shouldn't normally happen, but just in case canceling the previous future
// prevents the CPU from working on something that won't be used
this.bufferUploadFuture.cancel(true);
}
thisLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjLoadRefFutures);
this.adjacentLoadRefFutures = null;
this.bufferUploadFuture = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, renderSource, lodQuadBuilder);
this.bufferUploadFuture.thenAccept((buffer) ->
{
// upload complete, clean up the old data if
this.renderBuffer = buffer;
this.canRender = (buffer != null);
this.buildAndUploadRenderDataToGpuFuture = null;
this.bufferBuildFuture = null;
if (previousBuffer != null)
{
previousBuffer.close();
}
thisRenderSourceLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures);
this.adjacentLoadRefFutures = null;
});
});
}
catch (Exception e)
{
thisLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjLoadRefFutures);
thisRenderSourceLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures);
this.adjacentLoadRefFutures = null;
LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e);
this.uploadRenderDataToGpuFuture = null;
this.buildAndUploadRenderDataToGpuFuture = null;
this.bufferBuildFuture = null;
}
}
catch (Exception e)
{
thisLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjLoadRefFutures);
thisRenderSourceLoadFuture.decrementRefCount();
this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures);
this.adjacentLoadRefFutures = null;
LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e);
this.uploadRenderDataToGpuFuture = null;
this.buildAndUploadRenderDataToGpuFuture = null;
this.bufferBuildFuture = null;
}
});
}, executor);
@@ -245,17 +293,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
long adjPos = DhSectionPos.getAdjacentPos(this.pos, direction);
try
{
// ignore adjacent positions that aren't the same detail level
// since the LodDataBuilder can't handle different detail levels
byte detailLevel = this.quadTree.calculateExpectedDetailLevel(new DhBlockPos2D(MC.getPlayerBlockPos()), adjPos);
detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
if (detailLevel == DhSectionPos.getDetailLevel(this.pos))
LodRenderSection adjRenderSection = this.quadTree.getValue(adjPos);
if (adjRenderSection != null)
{
LodRenderSection adjRenderSection = this.quadTree.getValue(adjPos);
if (adjRenderSection != null)
{
futureArray[arrayIndex] = adjRenderSection.getRenderSourceAsync();
}
futureArray[arrayIndex] = adjRenderSection.getRenderSourceAsync();
}
}
catch (IndexOutOfBoundsException ignore) {}
@@ -316,6 +357,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.getRenderSourceLock.unlock();
}
}
private boolean isAdjacentPosSameDetailLevel(EDhDirection direction)
{
long adjPos = DhSectionPos.getAdjacentPos(this.pos, direction);
byte detailLevel = this.quadTree.calculateExpectedDetailLevel(new DhBlockPos2D(MC.getPlayerBlockPos()), adjPos);
detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
boolean adjacentIsSameDetailLevel = (detailLevel == DhSectionPos.getDetailLevel(this.pos));
return adjacentIsSameDetailLevel;
}
/**
@@ -324,8 +373,9 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
*/
public void cancelGpuUpload()
{
CompletableFuture<Void> future = this.uploadRenderDataToGpuFuture;
this.uploadRenderDataToGpuFuture = null;
CompletableFuture<Void> future = this.buildAndUploadRenderDataToGpuFuture;
this.buildAndUploadRenderDataToGpuFuture = null;
this.bufferBuildFuture = null;
if (future != null)
{
// interrupting the future speeds things up, but also causes
@@ -342,7 +392,39 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
public boolean canRender() { return this.canRender; }
public boolean gpuUploadInProgress() { return this.uploadRenderDataToGpuFuture != null; }
public boolean getRenderingEnabled() { return this.renderingEnabled; }
public void setRenderingEnabled(boolean enabled)
{
// some logic should only be run when enabling/disabling
// a section for the first time
boolean stateChanged = (this.renderingEnabled != enabled);
if (stateChanged)
{
if (enabled)
{
this.level.loadBeaconBeamsInPos(this.pos);
}
else
{
this.level.unloadBeaconBeamsInPos(this.pos);
if (Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get())
{
// show that this position has just been disabled
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(this.pos, 128f, 156f, 0.09f, Color.CYAN.darker()),
0.2, 32f
)
);
}
}
}
this.renderingEnabled = enabled;
}
public boolean gpuUploadInProgress() { return this.buildAndUploadRenderDataToGpuFuture != null; }
@@ -452,9 +534,18 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.renderBuffer.close();
}
if (this.uploadRenderDataToGpuFuture != null)
// cancel all in-progress futures since they aren't needed any more
if (this.buildAndUploadRenderDataToGpuFuture != null)
{
this.uploadRenderDataToGpuFuture.cancel(true);
this.buildAndUploadRenderDataToGpuFuture.cancel(true);
}
if (this.bufferBuildFuture != null)
{
this.bufferBuildFuture.cancel(true);
}
if (this.bufferUploadFuture != null)
{
this.bufferUploadFuture.cancel(true);
}
// this render section won't be rendering, we don't need to load any data for it
@@ -489,7 +580,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{
color = Color.green;
}
else if (this.uploadRenderDataToGpuFuture != null)
else if (this.buildAndUploadRenderDataToGpuFuture != null)
{
color = Color.yellow;
}
@@ -311,7 +311,7 @@ public class RenderBufferHandler implements AutoCloseable
}
ColumnRenderBuffer buffer = renderSection.renderBuffer;
if (buffer == null || !renderSection.renderingEnabled)
if (buffer == null || !renderSection.getRenderingEnabled())
{
continue;
}
@@ -193,17 +193,7 @@ public class GLProxy
return instance;
}
public EDhApiGpuUploadMethod getGpuUploadMethod()
{
EDhApiGpuUploadMethod method = Config.Client.Advanced.GpuBuffers.gpuUploadMethod.get();
if (!this.bufferStorageSupported && method == EDhApiGpuUploadMethod.BUFFER_STORAGE)
{
// if buffer storage isn't supported
// default to DATA since that is the most compatible
method = EDhApiGpuUploadMethod.DATA;
}
return method == EDhApiGpuUploadMethod.AUTO ? this.preferredUploadMethod : method;
}
public EDhApiGpuUploadMethod getGpuUploadMethod() { return this.preferredUploadMethod; }
public boolean runningOnRenderThread()
{
@@ -238,7 +238,7 @@ public class GLBuffer implements AutoCloseable
// buffer mapping //
//================//
public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpensionSize, int bufferHint, int mapFlags)
public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpansionSize, int bufferHint, int mapFlags)
{
LodUtil.assertTrue(targetSize != 0, "MapBuffer targetSize is 0");
LodUtil.assertTrue(uploadMethod.useEarlyMapping, "Upload method must be one that use early mappings in order to call mapBuffer");
@@ -252,7 +252,7 @@ public class GLBuffer implements AutoCloseable
if (this.size < targetSize || this.size > targetSize * BUFFER_SHRINK_TRIGGER)
{
int newSize = (int) (targetSize * BUFFER_EXPANSION_MULTIPLIER);
if (newSize > maxExpensionSize) newSize = maxExpensionSize;
if (newSize > maxExpansionSize) newSize = maxExpansionSize;
this.size = newSize;
if (this.bufferStorage)
{
@@ -77,9 +77,9 @@ public class GLVertexBuffer extends GLBuffer
this.vertexCount = vertCount;
}
public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpensionSize)
public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpansionSize)
{
return super.mapBuffer(targetSize, uploadMethod, maxExpensionSize,
return super.mapBuffer(targetSize, uploadMethod, maxExpansionSize,
uploadMethod.useBufferStorage ? GL32.GL_MAP_WRITE_BIT :
uploadMethod.useEarlyMapping ? GL32.GL_DYNAMIC_DRAW : GL32.GL_STATIC_DRAW,
GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT);
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.render.glObject.GLEnums;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL32;
import org.lwjgl.system.MemoryUtil;
import java.lang.invoke.MethodHandles;
import java.nio.ByteBuffer;
@@ -154,7 +155,7 @@ public class QuadElementBuffer extends GLElementBuffer
LOGGER.info("Quad IBO Resizing from [" + getCapacity() + "] to [" + quadCount + "]" + " with type: " +
GLEnums.getString(type));
ByteBuffer buffer = ByteBuffer.allocateDirect(indicesCount * GLEnums.getTypeSize(type)).order(ByteOrder.nativeOrder());
ByteBuffer buffer = MemoryUtil.memAlloc(indicesCount * GLEnums.getTypeSize(type));
buildBuffer(quadCount, buffer, type);
if (!gl.bufferStorageSupported)
{
@@ -169,6 +170,8 @@ public class QuadElementBuffer extends GLElementBuffer
super.uploadBuffer(buffer, EDhApiGpuUploadMethod.BUFFER_STORAGE,
indicesCount * GLEnums.getTypeSize(type), 0);
}
MemoryUtil.memFree(buffer);
}
}
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.AbstractVertexAttribute;
import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexPointer;
import org.lwjgl.opengl.GL32;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -86,14 +87,14 @@ public class ScreenQuad
private void createBuffer()
{
ByteBuffer buffer = ByteBuffer.allocateDirect(box_vertices.length * Float.BYTES);
buffer.order(ByteOrder.nativeOrder());
ByteBuffer buffer = MemoryUtil.memAlloc(box_vertices.length * Float.BYTES);
buffer.asFloatBuffer().put(box_vertices);
buffer.rewind();
this.boxBuffer = new GLVertexBuffer(false);
this.boxBuffer.bind();
this.boxBuffer.uploadBuffer(buffer, box_vertices.length, EDhApiGpuUploadMethod.DATA, box_vertices.length * Float.BYTES);
MemoryUtil.memFree(buffer);
}
}
@@ -22,7 +22,6 @@ package com.seibel.distanthorizons.core.render.renderer.generic;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.config.Config;
@@ -42,7 +41,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class BeaconRenderHandler
{
@@ -56,7 +54,7 @@ public class BeaconRenderHandler
private final BeaconBeamRepo beaconBeamRepo;
private final IDhApiRenderableBoxGroup beaconBoxGroup;
private final HashMap<DhBlockPos, AtomicInteger> beaconRefCountByBlockPos = new HashMap<>();
private final HashSet<DhBlockPos> beaconBlockPosSet = new HashSet<>();
@@ -96,8 +94,8 @@ public class BeaconRenderHandler
for (int i = 0; i < newBeamList.size(); i++)
{
BeaconBeamDTO beam = newBeamList.get(i);
newBeamByPos.put(beam.pos, beam);
allPosSet.add(beam.pos);
newBeamByPos.put(beam.blockPos, beam);
allPosSet.add(beam.blockPos);
}
// get existing beams
@@ -106,8 +104,8 @@ public class BeaconRenderHandler
for (int i = 0; i < existingBeamList.size(); i++)
{
BeaconBeamDTO beam = existingBeamList.get(i);
existingBeamByPos.put(beam.pos, beam);
allPosSet.add(beam.pos);
existingBeamByPos.put(beam.blockPos, beam);
allPosSet.add(beam.blockPos);
}
@@ -168,7 +166,7 @@ public class BeaconRenderHandler
for (int i = 0; i < existingBeamList.size(); i++)
{
BeaconBeamDTO beam = existingBeamList.get(i);
this.stopRenderingBeaconAtPos(beam.pos);
this.stopRenderingBeaconAtPos(beam.blockPos);
}
}
@@ -180,50 +178,37 @@ public class BeaconRenderHandler
private void startRenderingBeacon(BeaconBeamDTO beacon)
{
this.beaconRefCountByBlockPos.compute(beacon.pos, (beamPos, beaconRefCount) ->
if (this.beaconBlockPosSet.add(beacon.blockPos))
{
if (beaconRefCount == null) { beaconRefCount = new AtomicInteger(); }
if (beaconRefCount.getAndIncrement() == 0)
{
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
new DhApiVec3d(beacon.pos.x, beacon.pos.y+1, beacon.pos.z),
new DhApiVec3d(beacon.pos.x+1, BEAM_TOP_Y, beacon.pos.z+1),
beacon.color,
EDhApiBlockMaterial.ILLUMINATED
);
this.beaconBoxGroup.add(beaconBox);
this.beaconBoxGroup.triggerBoxChange();
}
return beaconRefCount;
});
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
new DhApiVec3d(beacon.blockPos.x, beacon.blockPos.y+1, beacon.blockPos.z),
new DhApiVec3d(beacon.blockPos.x+1, BEAM_TOP_Y, beacon.blockPos.z+1),
beacon.color,
EDhApiBlockMaterial.ILLUMINATED
);
this.beaconBoxGroup.add(beaconBox);
this.beaconBoxGroup.triggerBoxChange();
}
}
private void stopRenderingBeaconAtPos(DhBlockPos beaconPos)
{
this.beaconRefCountByBlockPos.compute(beaconPos, (pos, beaconRefCount) ->
if (this.beaconBlockPosSet.remove(beaconPos))
{
if (beaconRefCount != null
&& beaconRefCount.decrementAndGet() <= 0)
this.beaconBoxGroup.removeIf((box) ->
{
this.beaconBoxGroup.removeIf((box) ->
box.minPos.x == beaconPos.x
&& box.minPos.y == beaconPos.y+1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == beaconPos.z
);
this.beaconBoxGroup.triggerBoxChange();
return null;
}
else
{
return beaconRefCount;
}
});
return box.minPos.x == beaconPos.x
&& box.minPos.y == beaconPos.y+1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == beaconPos.z;
});
this.beaconBoxGroup.triggerBoxChange();
}
}
private void updateBeaconColor(BeaconBeamDTO newBeam)
{
DhBlockPos pos = newBeam.pos;
DhBlockPos pos = newBeam.blockPos;
for (int i = 0; i < this.beaconBoxGroup.size(); i++)
{
DhApiRenderableBox box = this.beaconBoxGroup.get(i);
@@ -50,6 +50,7 @@ import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.ARBInstancedArrays;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL33;
import org.lwjgl.system.MemoryUtil;
import java.awt.*;
import java.nio.ByteBuffer;
@@ -192,24 +193,22 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
private void createBuffers()
{
// box vertices
ByteBuffer boxVerticesBuffer = ByteBuffer.allocateDirect(BOX_VERTICES.length * Float.BYTES);
boxVerticesBuffer.order(ByteOrder.nativeOrder());
ByteBuffer boxVerticesBuffer = MemoryUtil.memAlloc(BOX_VERTICES.length * Float.BYTES);
boxVerticesBuffer.asFloatBuffer().put(BOX_VERTICES);
boxVerticesBuffer.rewind();
this.boxVertexBuffer = new GLVertexBuffer(false);
this.boxVertexBuffer.bind();
this.boxVertexBuffer.uploadBuffer(boxVerticesBuffer, 8, EDhApiGpuUploadMethod.DATA, BOX_VERTICES.length * Float.BYTES);
MemoryUtil.memFree(boxVerticesBuffer);
// box vertex indexes
ByteBuffer solidIndexBuffer = ByteBuffer.allocateDirect(BOX_INDICES.length * Integer.BYTES);
solidIndexBuffer.order(ByteOrder.nativeOrder());
ByteBuffer solidIndexBuffer = MemoryUtil.memAlloc(BOX_INDICES.length * Integer.BYTES);
solidIndexBuffer.asIntBuffer().put(BOX_INDICES);
solidIndexBuffer.rewind();
this.boxIndexBuffer = new GLElementBuffer(false);
this.boxIndexBuffer.uploadBuffer(solidIndexBuffer, EDhApiGpuUploadMethod.DATA, BOX_INDICES.length * Integer.BYTES, GL32.GL_STATIC_DRAW);
this.boxIndexBuffer.bind();
MemoryUtil.memFree(solidIndexBuffer);
}
private void addGenericDebugObjects()
{
@@ -103,17 +103,19 @@ public class SSAOShader extends AbstractShaderRenderer
this.shader.setUniform(this.gSampleCountUniform,
Config.Client.Advanced.Graphics.Ssao.sampleCount.get());
this.shader.setUniform(this.gRadiusUniform,
Config.Client.Advanced.Graphics.Ssao.radius.get().floatValue());
// Implicit Number cast needs to be done to prevent issues with the default value being a int
Number radius = Config.Client.Advanced.Graphics.Ssao.radius.get();
this.shader.setUniform(this.gRadiusUniform, radius.floatValue());
this.shader.setUniform(this.gStrengthUniform,
Config.Client.Advanced.Graphics.Ssao.strength.get().floatValue());
this.shader.setUniform(this.gMinLightUniform,
Config.Client.Advanced.Graphics.Ssao.minLight.get().floatValue());
Number strength = Config.Client.Advanced.Graphics.Ssao.strength.get();
this.shader.setUniform(this.gStrengthUniform, strength.floatValue());
this.shader.setUniform(this.gBiasUniform,
Config.Client.Advanced.Graphics.Ssao.bias.get().floatValue());
Number minLight = Config.Client.Advanced.Graphics.Ssao.minLight.get();
this.shader.setUniform(this.gMinLightUniform, minLight.floatValue());
Number bias = Config.Client.Advanced.Graphics.Ssao.bias.get();
this.shader.setUniform(this.gBiasUniform, bias.floatValue());
GL32.glActiveTexture(GL32.GL_TEXTURE0);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, LodRenderer.getActiveDepthTextureId());
@@ -21,14 +21,13 @@ package com.seibel.distanthorizons.core.sql.dto;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import java.awt.*;
/** handles storing {@link FullDataSourceV2}'s in the database. */
public class BeaconBeamDTO implements IBaseDTO<DhBlockPos>
{
public DhBlockPos pos;
public DhBlockPos blockPos;
public Color color;
@@ -37,9 +36,9 @@ public class BeaconBeamDTO implements IBaseDTO<DhBlockPos>
// constructor //
//=============//
public BeaconBeamDTO(DhBlockPos pos, Color color)
public BeaconBeamDTO(DhBlockPos blockPos, Color color)
{
this.pos = pos;
this.blockPos = blockPos;
this.color = color;
}
@@ -50,6 +49,6 @@ public class BeaconBeamDTO implements IBaseDTO<DhBlockPos>
//===========//
@Override
public DhBlockPos getKey() { return this.pos; }
public DhBlockPos getKey() { return this.blockPos; }
}
@@ -100,9 +100,9 @@ public class BeaconBeamRepo extends AbstractDhRepo<DhBlockPos, BeaconBeamDTO>
PreparedStatement statement = this.createPreparedStatement(sql);
int i = 1;
statement.setObject(i++, dto.pos.x);
statement.setObject(i++, dto.pos.y);
statement.setObject(i++, dto.pos.z);
statement.setObject(i++, dto.blockPos.x);
statement.setObject(i++, dto.blockPos.y);
statement.setObject(i++, dto.blockPos.z);
statement.setObject(i++, dto.color.getRed());
statement.setObject(i++, dto.color.getGreen());
@@ -132,9 +132,9 @@ public class BeaconBeamRepo extends AbstractDhRepo<DhBlockPos, BeaconBeamDTO>
statement.setObject(i++, System.currentTimeMillis()); // last modified unix time
statement.setObject(i++, dto.pos.x);
statement.setObject(i++, dto.pos.y);
statement.setObject(i++, dto.pos.z);
statement.setObject(i++, dto.blockPos.x);
statement.setObject(i++, dto.blockPos.y);
statement.setObject(i++, dto.blockPos.z);
return statement;
}
@@ -20,7 +20,6 @@
package com.seibel.distanthorizons.core.util;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletionException;
@@ -29,11 +28,8 @@ import java.util.concurrent.RejectedExecutionException;
import com.seibel.distanthorizons.api.enums.config.EDhApiVanillaOverdraw;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.Pos2D;
import com.seibel.distanthorizons.core.render.vertexFormat.DefaultLodVertexFormats;
import com.seibel.distanthorizons.core.render.vertexFormat.LodVertexFormat;
import com.seibel.distanthorizons.core.util.gridList.EdgeDistanceBooleanGrid;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
@@ -44,9 +40,6 @@ import org.apache.logging.log4j.Logger;
/**
* This class holds methods and constants that may be used in multiple places.
*
* @author James Seibel
* @version 2022-12-5
*/
public class LodUtil
{
@@ -54,31 +47,7 @@ public class LodUtil
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/**
* Vanilla render distances less than or equal to this will not allow partial
* overdraw. The VanillaOverdraw will either be ALWAYS or NEVER.
*/
public static final int MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW = 4;
/**
* Vanilla render distances less than or equal to this will cause the overdraw to
* run at a smaller fraction of the vanilla render distance.
*/
public static final int MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW = 11;
/**
* alpha used when drawing chunks in debug mode
*/
public static final int DEBUG_ALPHA = 255; // 0 - 25;
public static final int COLOR_DEBUG_BLACK = ColorUtil.rgbToInt(DEBUG_ALPHA, 0, 0, 0);
public static final int COLOR_DEBUG_WHITE = ColorUtil.rgbToInt(DEBUG_ALPHA, 255, 255, 255);
public static final int COLOR_INVISIBLE = ColorUtil.rgbToInt(0, 0, 0, 0);
//FIXME: WE NEED MORE COLORS!!!!
/**
* In order of nearest to farthest: <br>
* Red, Orange, Yellow, Green, Cyan, Blue, Magenta, white, gray, black
@@ -166,92 +135,9 @@ public class LodUtil
/**
* Gets the ServerWorld for the relevant dimension.
*
* @return null if there is no ServerWorld for the given dimension
*/
public static ILevelWrapper getServerWorldFromDimension(IDimensionTypeWrapper newDimension)
{
if (!MC_CLIENT.hasSinglePlayerServer())
return null;
Iterable<ILevelWrapper> worlds = MC_CLIENT.getAllServerWorlds();
ILevelWrapper returnWorld = null;
for (ILevelWrapper world : worlds)
{
if (world.getDimensionType() == newDimension)
{
returnWorld = world;
break;
}
}
return returnWorld;
}
public static int computeOverdrawOffset()
{
int chunkRenderDist = MC_RENDER.getRenderDistance() + 1;
EDhApiVanillaOverdraw overdraw = EDhApiVanillaOverdraw.ALWAYS; //Config.Client.Advanced.Graphics.AdvancedGraphics.vanillaOverdraw.get();
if (overdraw == EDhApiVanillaOverdraw.ALWAYS) return Integer.MAX_VALUE;
int offset;
if (overdraw == EDhApiVanillaOverdraw.NEVER)
{
offset = 0; //Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawOffset.get();
}
else
{
if (chunkRenderDist < MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW)
{
offset = 1;
}
else
{
offset = chunkRenderDist / 5;
}
}
if (chunkRenderDist - offset <= 1)
{
return Integer.MAX_VALUE;
}
return offset;
}
/** not currently used since the new rendering system can't easily toggle single chunks to render */
@Deprecated
public static EdgeDistanceBooleanGrid readVanillaRenderedChunks()
{
int offset = computeOverdrawOffset();
if (offset == Integer.MAX_VALUE) return null;
int renderDist = MC_RENDER.getRenderDistance() + 1;
Iterator<DhChunkPos> posIter = MC_RENDER.getVanillaRenderedChunks().iterator();
return new EdgeDistanceBooleanGrid(new Iterator<Pos2D>()
{
@Override
public boolean hasNext()
{
return posIter.hasNext();
}
@Override
public Pos2D next()
{
DhChunkPos pos = posIter.next();
return new Pos2D(pos.x, pos.z);
}
},
MC_CLIENT.getPlayerChunkPos().x - renderDist,
MC_CLIENT.getPlayerChunkPos().z - renderDist,
renderDist * 2 + 1);
}
//=========//
// methods //
//=========//
/** Returns the chunk int position for the given double position */
public static int getChunkPosFromDouble(double value) { return (int) Math.floor(value / CHUNK_WIDTH); }
@@ -275,11 +161,6 @@ public class LodUtil
return true;
}
public static void checkInterrupts() throws InterruptedException
{
if (Thread.interrupted()) throw new InterruptedException();
}
/**
* Format a given string with params using log4j's MessageFormat
*
@@ -290,26 +171,7 @@ public class LodUtil
* Do not use it for deserialization or naming of objects.</b>
* @author leetom
*/
public static String formatLog(String str, Object... param)
{
return LOGGER.getMessageFactory().newMessage(str, param).getFormattedMessage();
}
/**
* Returns a shortened version of the given string that is no longer than maxLength. <br>
* If null returns the empty string.
*/
public static String shortenString(String str, int maxLength)
{
if (str == null)
{
return "";
}
else
{
return str.substring(0, Math.min(str.length(), maxLength));
}
}
public static String formatLog(String str, Object... param) { return LOGGER.getMessageFactory().newMessage(str, param).getFormattedMessage(); }
public static class AssertFailureException extends RuntimeException
{
@@ -48,93 +48,6 @@ public class RenderUtil
//=================//
// culling methods //
//=================//
/**
* Returns if the given ChunkPos is in the loaded area of the world.
*
* @param center the center of the loaded world (probably the player's ChunkPos)
*/
public static boolean isChunkPosInLoadedArea(DhChunkPos pos, DhChunkPos center)
{
return (pos.x >= center.x - MC_RENDER.getRenderDistance()
&& pos.x <= center.x + MC_RENDER.getRenderDistance())
&&
(pos.z >= center.z - MC_RENDER.getRenderDistance()
&& pos.z <= center.z + MC_RENDER.getRenderDistance());
}
/**
* Returns if the given coordinate is in the loaded area of the world.
*
* @param centerCoordinate the center of the loaded world
*/
public static boolean isCoordinateInLoadedArea(int x, int z, int centerCoordinate)
{
return (x >= centerCoordinate - MC_RENDER.getRenderDistance()
&& x <= centerCoordinate + MC_RENDER.getRenderDistance())
&&
(z >= centerCoordinate - MC_RENDER.getRenderDistance()
&& z <= centerCoordinate + MC_RENDER.getRenderDistance());
}
/**
* Find the coordinates that are in the center half of the given
* 2D matrix, starting at (0,0) and going to (2 * lodRadius, 2 * lodRadius).
*/
public static boolean isCoordinateInNearFogArea(int i, int j, int lodRadius)
{
int halfRadius = lodRadius / 2;
return (i >= lodRadius - halfRadius
&& i <= lodRadius + halfRadius)
&&
(j >= lodRadius - halfRadius
&& j <= lodRadius + halfRadius);
}
/**
* Returns true if one of the region's 4 corners is in front
* of the camera.
*/
public static boolean isRegionInViewFrustum(DhBlockPos playerBlockPos, Vec3f cameraDir, int vboRegionX, int vboRegionZ)
{
// convert the vbo position into a direction vector
// starting from the player's position
Vec3f vboVec = new Vec3f(vboRegionX * LodUtil.REGION_WIDTH, 0, vboRegionZ * LodUtil.REGION_WIDTH);
Vec3f playerVec = new Vec3f(playerBlockPos.x, playerBlockPos.y, playerBlockPos.z);
vboVec.subtract(playerVec);
// calculate the 4 corners
Vec3f vboSeVec = new Vec3f(vboVec.x + LodUtil.REGION_WIDTH, vboVec.y, vboVec.z + LodUtil.REGION_WIDTH);
Vec3f vboSwVec = new Vec3f(vboVec.x, vboVec.y, vboVec.z + LodUtil.REGION_WIDTH);
Vec3f vboNwVec = new Vec3f(vboVec.x, vboVec.y, vboVec.z);
Vec3f vboNeVec = new Vec3f(vboVec.x + LodUtil.REGION_WIDTH, vboVec.y, vboVec.z);
// if any corner is visible, this region should be rendered
return isNormalizedVectorInViewFrustum(vboSeVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboSwVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboNwVec, cameraDir) ||
isNormalizedVectorInViewFrustum(vboNeVec, cameraDir);
}
/**
* Currently takes the dot product of the two vectors,
* but in the future could do more complicated frustum culling tests.
*/
private static boolean isNormalizedVectorInViewFrustum(Vec3f objectVector, Vec3f cameraDir)
{
// the -0.1 is to offer a slight buffer, so we are
// more likely to render LODs and thus, hopefully prevent
// flickering or odd disappearances
return objectVector.dotProduct(cameraDir) > -0.1;
}
//=====================//
// matrix manipulation //
//=====================//
@@ -166,19 +79,6 @@ public class RenderUtil
return mcModelViewMat.copy();
}
/**
* create and return a new combined modelView/projection matrix based on MC's modelView and projection matrices
*
* @param mcProjMat Minecraft's current projection matrix
* @param mcModelViewMat Minecraft's current model view matrix
*/
public static Mat4f createCombinedModelViewProjectionMatrix(Mat4f mcProjMat, Mat4f mcModelViewMat, float partialTicks)
{
Mat4f lodProj = createLodProjectionMatrix(mcProjMat, partialTicks);
lodProj.multiply(createLodModelViewMatrix(mcModelViewMat));
return lodProj;
}
public static float getNearClipPlaneDistanceInBlocks(float partialTicks)
{
int chunkRenderDistance = MC_RENDER.getRenderDistance();
@@ -1,100 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.core.util.gridList;
import com.seibel.distanthorizons.core.pos.Pos2D;
import com.seibel.distanthorizons.core.util.objects.BoolType;
import java.util.Iterator;
import java.util.function.IntPredicate;
public class EdgeDistanceBooleanGrid extends PosArrayGridList<BoolType>
{
ArrayGridList<Integer> edgeCache = null;
public EdgeDistanceBooleanGrid(Iterator<Pos2D> posIter, int offsetX, int offsetY, int gridSize)
{
super(gridSize, offsetX, offsetY);
while (posIter.hasNext())
{
Pos2D p = posIter.next();
this.set(p, BoolType.TRUE);
}
}
// Return false if it is indeed updated
private static boolean updatePos(ArrayGridList<Integer> grid, int ox, int oy)
{
if (grid.get(ox, oy) < 0) return true;
if (ox == 0 || oy == 0 || ox == grid.gridSize - 1 || oy == grid.gridSize - 1)
{
return true;
}
int v = grid.get(ox, oy);
if (
grid.get(ox, oy + 1) < v ||
grid.get(ox, oy - 1) < v ||
grid.get(ox + 1, oy) < v ||
grid.get(ox - 1, oy) < v
)
{
return true;
}
else
{
grid.set(ox, oy, v + 1);
return false;
}
}
//FIXME: This is slow and expensive. Use queue to make this skip recheck done pos
private void computeEdgeCache()
{
if (edgeCache != null) return;
edgeCache = new ArrayGridList<Integer>(gridSize, (ox, oy) -> {
BoolType b = get(ox + getOffsetX(), oy + getOffsetY());
return b == null ? -1 : 0;
});
final boolean[] isDone = {false};
while (!isDone[0])
{
isDone[0] = true;
edgeCache.forEachPos((ox, oy) -> {
isDone[0] &= updatePos(edgeCache, ox, oy);
});
}
}
// 0 means right on the edge, while 1 means 1 ceil away. Uses Manhattan Distance
public <T extends ArrayGridList<BoolType>> void flagAllWithDistance(T list, IntPredicate predicate)
{
computeEdgeCache();
edgeCache.forEachPos((ox, oy) -> {
int v = edgeCache.get(ox, oy);
if (v < 0 || !predicate.test(v)) return;
list.set(ox + getOffsetX(), oy + getOffsetY(), BoolType.TRUE);
});
}
}
@@ -141,6 +141,25 @@ public class ChunkLightStorage
lightSection.set(x, y, z, lightLevel);
}
public void clear()
{
if (this.lightSections != null)
{
for (int i = 0; i < this.lightSections.length; i++)
{
LightSection section = this.lightSections[i];
if (section != null)
{
section.constantValue = LodUtil.MIN_MC_LIGHT;
if (section.data != null)
{
Arrays.fill(section.data, 0L);
}
}
}
}
}
//================//
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -79,9 +80,11 @@ public interface IChunkWrapper extends IBindable
int getDhSkyLight(int relX, int relY, int relZ);
void setDhSkyLight(int relX, int relY, int relZ, int lightValue);
void clearDhSkyLighting();
int getDhBlockLight(int relX, int relY, int relZ);
void setDhBlockLight(int relX, int relY, int relZ, int lightValue);
void clearDhBlockLighting();
int getBlockLight(int relX, int relY, int relZ);
int getSkyLight(int relX, int relY, int relZ);
@@ -150,29 +153,77 @@ public interface IChunkWrapper extends IBindable
* This is generally done in cases where MC's lighting is correct now, but may not be later (like when a chunk is unloading).
*
* @throws IllegalStateException if the chunk's lighting isn't valid. This is done to prevent accidentally baking broken lighting.
* @return true if the chunk's lighting was successfully populated, false otherwise
*/
default void bakeDhLightingUsingMcLightingEngine() throws IllegalStateException
@Deprecated
default boolean bakeDhLightingUsingMcLightingEngine(ILevelWrapper levelWrapper) throws IllegalStateException
{
if (!this.isLightCorrect())
{
throw new IllegalStateException("Unable to bake lighting for for chunk [" + this.getChunkPos() + "], Minecraft lighting not valid.");
return false;
}
// get the lighting for every relative block pos
//=======================//
// get lighting for each //
// relative block pos //
//=======================//
boolean lightingFound = false;
// if the level doesn't have sky lights, then this check can be ignored
// since all sky light values will be 0 anyway
boolean skyLightingFound = !levelWrapper.hasSkyLight();
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++)
{
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++)
{
this.setDhSkyLight(relX, y, relZ, this.getSkyLight(relX, y, relZ));
this.setDhBlockLight(relX, y, relZ, this.getBlockLight(relX, y, relZ));
int skyLight = this.getSkyLight(relX, y, relZ);
this.setDhSkyLight(relX, y, relZ, skyLight);
int blockLight = this.getBlockLight(relX, y, relZ);
this.setDhBlockLight(relX, y, relZ, blockLight);
// MC defaults to max sky light and no block light, including underground blocks.
// If any position has something different then those default values, it's likely that the
// lighting was properly populated for at least part of the chunk
if (!lightingFound &&
(skyLight != LodUtil.MAX_MC_LIGHT || blockLight != LodUtil.MIN_MC_LIGHT))
{
lightingFound = true;
}
if (!skyLightingFound
&& skyLight != LodUtil.MIN_MC_LIGHT)
{
skyLightingFound = true;
}
}
}
}
//=================//
// validate result //
//=================//
// if no lighting was found or the sky is always black, the lighting is likely broken
if (!lightingFound || !skyLightingFound
// if lighting is no longer correct or doesn't match the saved values
// its very likely it broke halfway through and will need regenerating
|| !this.isLightCorrect()
|| this.getSkyLight(0, 0, 0) != this.getDhSkyLight(0,0,0)
|| this.getBlockLight(0, 0, 0) != this.getDhBlockLight(0,0,0))
{
return false;
}
// lighting is valid
this.setIsDhLightCorrect(true);
this.setUseDhLighting(true);
return true;
}
@@ -229,18 +280,42 @@ public interface IChunkWrapper extends IBindable
int hash = 31;
int primeBlockMultiplier = 227;
int primeBiomeMultiplier = 701;
int primeHeightMultiplier = 137;
int minBuildHeight = this.getMinBuildHeight();
int maxBuildHeight = this.getMaxBuildHeight();
int minBuildHeight = this.getMaxNonEmptyHeight();
int maxBuildHeight = this.getMinNonEmptyHeight();
// most blocks (only some blocks are sampled since checking every block is a very slow operation)
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x+=2)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z+=2)
{
for (int y = minBuildHeight; y < maxBuildHeight; y+=8)
{
hash = (hash * primeBlockMultiplier) + this.getBlockState(x, y, z).hashCode();
hash = (hash * primeBiomeMultiplier) + this.getBiome(x, y, z).hashCode();
hash = (hash * primeHeightMultiplier) + y;
}
}
}
// surface (this should cover most cases for when users modify chunks)
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
for (int y = minBuildHeight; y < maxBuildHeight; y++)
int lightBlockingY = this.getLightBlockingHeightMapValue(x, z);
hash = (hash * primeBlockMultiplier) + this.getBlockState(x, lightBlockingY, z).hashCode();
hash = (hash * primeBiomeMultiplier) + this.getBiome(x, lightBlockingY, z).hashCode();
hash = (hash * primeHeightMultiplier) + lightBlockingY;
int solidY = this.getSolidHeightMapValue(x, z);
if (solidY != lightBlockingY)
{
hash = (hash * primeBlockMultiplier) + this.getBlockState(x, y, z).hashCode();
hash = (hash * primeBiomeMultiplier) + this.getBiome(x, y, z).hashCode();
hash = (hash * primeBlockMultiplier) + this.getBlockState(x, solidY, z).hashCode();
hash = (hash * primeBiomeMultiplier) + this.getBiome(x, solidY, z).hashCode();
hash = (hash * primeHeightMultiplier) + solidY;
}
}
}
@@ -48,18 +48,10 @@ public interface IMinecraftRenderWrapper extends IBindable
{
Vec3f getLookAtVector();
DhBlockPos getCameraBlockPosition();
boolean playerHasBlindingEffect();
Vec3d getCameraExactPosition();
Mat4f getWorldViewMatrix();
Mat4f getDefaultProjectionMatrix(float partialTicks);
double getGamma();
Color getFogColor(float partialTicks);
default Color getSpecialFogColor(float partialTicks) { return getFogColor(partialTicks); }
@@ -90,63 +82,6 @@ public interface IMinecraftRenderWrapper extends IBindable
*/
void clearTargetFrameBuffer();
/**
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame.
* <br>
* If not implemented this calls {@link #getMaximumRenderedChunks()}.
*/
default HashSet<DhChunkPos> getVanillaRenderedChunks()
{
// FIXME: Is this actually required? Does it make a differance if it exists or not?
ISodiumAccessor sodium = ModAccessorInjector.INSTANCE.get(ISodiumAccessor.class);
return sodium == null ? getMaximumRenderedChunks() : sodium.getNormalRenderedChunks();
}
static boolean correctedCheckRadius(int dx, int dz, int radius2Mul4)
{
dx = dx * 2;// + (dx < 0 ? -1 : 1);
dz = dz * 2;// + (dz < 0 ? -1 : 1);
return (dx * dx + dz * dz <= radius2Mul4);
}
/**
* <strong>Doesn't need to be implemented.</strong> <br>
* Returns every chunk position within the vanilla render distance.
*/
default HashSet<DhChunkPos> getMaximumRenderedChunks()
{
IMinecraftClientWrapper mcWrapper = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
IWrapperFactory factory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
IVersionConstants versionConstants = SingletonInjector.INSTANCE.get(IVersionConstants.class);
IMinecraftClientWrapper minecraft = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
ILevelWrapper clientWorld = minecraft.getWrappedClientLevel();
int chunkDist = this.getRenderDistance() + 1; // For some reason having '+1' is actually closer to real value
DhChunkPos centerChunkPos = mcWrapper.getPlayerChunkPos();
int centerChunkX = centerChunkPos.x;
int centerChunkZ = centerChunkPos.z;
int chunkDist2Mul4 = chunkDist * chunkDist * 4;
// add every position within render distance
HashSet<DhChunkPos> renderedPos = new HashSet<DhChunkPos>();
for (int deltaChunkX = -chunkDist; deltaChunkX <= chunkDist; deltaChunkX++)
{
for (int deltaChunkZ = -chunkDist; deltaChunkZ <= chunkDist; deltaChunkZ++)
{
if (!versionConstants.isVanillaRenderedChunkSquare() &&
!correctedCheckRadius(deltaChunkX, deltaChunkZ, chunkDist2Mul4))
{
continue;
}
if (!clientWorld.hasChunkLoaded(centerChunkX + deltaChunkX, centerChunkZ + deltaChunkZ)) continue;
renderedPos.add(new DhChunkPos(centerChunkX + deltaChunkX, centerChunkZ + deltaChunkZ));
}
}
return renderedPos;
}
/** Can return null if the given level hasn't had a light map assigned to it */
@Nullable
ILightMapWrapper getLightmapWrapper(ILevelWrapper level);
@@ -25,8 +25,6 @@ import java.util.HashSet;
public interface ISodiumAccessor extends IModAccessor
{
HashSet<DhChunkPos> getNormalRenderedChunks();
/** A temporary overwrite for a config in sodium 0.5 to fix their terrain from showing, will be removed once a proper fix is added */
void setFogOcclusion(boolean b); // FIXME
@@ -587,6 +587,8 @@
"Show Low Memory Warning",
"distanthorizons.config.client.advanced.logging.showReplayWarningOnStartup":
"Show Replay Warning",
"distanthorizons.config.client.advanced.logging.showModCompatibilityWarningsOnStartup":
"Show Mod Compatibility Warnings",
@@ -350,6 +350,8 @@ public class LightingTestChunkWrapper implements IChunkWrapper
}
return this.blockLightStorage;
}
@Override
public void clearDhBlockLighting() { throw new UnsupportedOperationException("Not implemented"); }
@Override
@@ -364,6 +366,8 @@ public class LightingTestChunkWrapper implements IChunkWrapper
this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ);
this.getSkyLightStorage().set(relX, y, relZ, lightValue);
}
@Override
public void clearDhSkyLighting() { throw new UnsupportedOperationException("Not implemented"); }
private ChunkLightStorage getSkyLightStorage()
{