Compare commits

...

27 Commits

Author SHA1 Message Date
James Seibel e9a044308f remove dev from version number 2025-12-18 09:35:07 -06:00
James Seibel 1aabc0c792 remove chunkWrapper.isStillValid() 2025-12-18 09:35:02 -06:00
James Seibel b1b0642fbe LodRenderSection commenting/regions 2025-12-17 09:32:12 -06:00
James Seibel eecb28d11f Fix GLProxy error in multiplayer
Make some GLProxy methods static to prevent setup order issues
2025-12-17 09:02:07 -06:00
James Seibel 90564f2537 fix javadoc in LevelWrapper 2025-12-16 16:39:03 -06:00
James Seibel ded0b979cf Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons-core 2025-12-16 14:45:57 -06:00
James Seibel ed9cc5485c Add SSAO fade out distance 2025-12-16 14:45:53 -06:00
s809 cbd5974657 Fix packet handle errors not showing on F3 screen 2025-12-17 00:15:55 +05:00
James Seibel 0e5fba58ab minor shader program refactor 2025-12-16 09:13:22 -06:00
James Seibel 2943e63382 slight light engine optimization 2025-12-15 14:37:15 -06:00
James Seibel 30564aade7 up version number 2.4.2 -> 2.4.3-dev 2025-12-15 10:17:28 -06:00
James Seibel aabb90ada6 remove dev from version number 2025-12-15 09:00:15 -06:00
James Seibel 963a8dc53f comment LevelWrapper getDimensionName() 2025-12-15 08:55:40 -06:00
James Seibel aa6d69385b Move GC warning into the log 2025-12-15 08:44:06 -06:00
James Seibel f42c9cf8fb Improve initial library check error handling 2025-12-14 22:29:08 -06:00
James Seibel 92e0011c8d Fix auto update success dialog 2025-12-14 21:50:56 -06:00
James Seibel c20d95a7c7 improve spacing for self updater version log 2025-12-14 21:21:45 -06:00
James Seibel 353aa1ed2c maybe improve ZStd version check 2025-12-14 21:20:42 -06:00
James Seibel 5aa43ebcc8 hide LODs when underwater 2025-12-14 17:22:35 -06:00
James Seibel b6145461b6 add note to ignored block CSV 2025-12-14 17:02:53 -06:00
James Seibel 478e431076 up version number 2.4.1 -> 2.4.2-dev 2025-12-14 17:00:34 -06:00
James Seibel 6feb7f1b42 remove dev from version number 2025-12-14 13:46:04 -06:00
James Seibel 016fc66293 Print a warning if G1GC is used
G1GC is known to cause stuttering
2025-12-13 16:46:59 -06:00
James Seibel 6d3e30d425 add Zstd decompress lib check in initalizer 2025-12-13 15:48:05 -06:00
James Seibel 5be5c5a5bc replace client ticks with a timer
Prevents DH loading issues when MC ticks are paused
2025-12-13 11:19:33 -06:00
James Seibel ed5aeb8951 minor texture setup reformatting 2025-12-13 10:43:01 -06:00
James Seibel 7f0ddadf26 up version number 2.4.0 -> 2.4.1-dev 2025-12-13 10:20:44 -06:00
35 changed files with 465 additions and 384 deletions
@@ -38,7 +38,7 @@ public final class ModInfo
public static final String NAME = "DistantHorizons"; public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */ /** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons"; public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.4.0-b"; public static final String VERSION = "2.4.3-b";
/** Returns true if the current build is an unstable developer build, false otherwise. */ /** Returns true if the current build is an unstable developer build, false otherwise. */
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev"); public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
@@ -20,36 +20,41 @@
package com.seibel.distanthorizons.core; package com.seibel.distanthorizons.core;
import com.github.luben.zstd.ZstdOutputStream; import com.github.luben.zstd.ZstdOutputStream;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory; import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
import com.seibel.distanthorizons.core.sql.DatabaseUpdater; import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.world.DhApiWorldProxy; import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
import com.seibel.distanthorizons.core.api.external.methods.config.DhApiConfig; import com.seibel.distanthorizons.core.api.external.methods.config.DhApiConfig;
import com.seibel.distanthorizons.core.api.external.methods.data.DhApiTerrainDataRepo; import com.seibel.distanthorizons.core.api.external.methods.data.DhApiTerrainDataRepo;
import com.seibel.distanthorizons.api.DhApi; import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy; import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import net.jpountz.lz4.LZ4FrameOutputStream; import net.jpountz.lz4.LZ4FrameOutputStream;
import org.apache.logging.log4j.LogManager;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.sqlite.SQLiteJDBCLoader; import org.sqlite.SQLiteJDBCLoader;
import org.sqlite.util.OSInfo;
import org.tukaani.xz.XZOutputStream; import org.tukaani.xz.XZOutputStream;
import java.awt.*; import java.lang.management.GarbageCollectorMXBean;
import java.io.File; import java.lang.management.ManagementFactory;
import java.util.List;
/** Handles first time Core setup. */ /** Handles first time Core setup. */
public class Initializer public class Initializer
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static void init() public static void init()
{ {
LOGGER.info("Running library validation...");
// confirm that all referenced libraries are available to use // confirm that all referenced libraries are available to use
try try
{ {
@@ -57,6 +62,17 @@ public class Initializer
// will throw an error (not an exception) // will throw an error (not an exception)
Class<?> lz4Compressor = LZ4FrameOutputStream.class; Class<?> lz4Compressor = LZ4FrameOutputStream.class;
Class<?> zstdCompressor = ZstdOutputStream.class; Class<?> zstdCompressor = ZstdOutputStream.class;
{
byte[] testCompressByteArray = new byte[1024];
for (int i = 0; i < testCompressByteArray.length; i++)
{
testCompressByteArray[i] = (byte) (i % 126);
}
byte[] compressedBytes = com.github.luben.zstd.Zstd.compress(testCompressByteArray);
com.github.luben.zstd.Zstd.decompress(compressedBytes);
}
Class<?> lzmaCompressor = XZOutputStream.class; Class<?> lzmaCompressor = XZOutputStream.class;
//Class<?> networking = ByteBuf.class; //Class<?> networking = ByteBuf.class;
Class<?> config = com.electronwill.nightconfig.core.Config.class; Class<?> config = com.electronwill.nightconfig.core.Config.class;
@@ -73,9 +89,7 @@ public class Initializer
} }
catch (Throwable e) catch (Throwable e)
{ {
LOGGER.fatal("Critical programmer error: One or more libraries aren't present. Error: [" + e.getMessage() + "].", e); MC_CLIENT.crashMinecraft("Distant Horizons critical setup error: One or more libraries are either in-accessible, corrupted, or overwritten by another mod. Error: [" + e.getMessage() + "].", e);
// throwing here should crash the game, notifying the developer that something is wrong
throw new RuntimeException(e);
} }
// confirm the resource directory is present // confirm the resource directory is present
@@ -89,8 +103,7 @@ public class Initializer
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.fatal("Critical programmer error: Can't read SQL Scripts resource folder is either missing or malformed. Error: [" + e.getMessage() + "]."); MC_CLIENT.crashMinecraft("Critical programmer error: Can't read SQL Scripts resource folder is either missing or malformed. Error: [" + e.getMessage() + "].", e);
throw new RuntimeException(e);
} }
// This code has been disabled since it can cause Mac // This code has been disabled since it can cause Mac
@@ -121,6 +134,43 @@ public class Initializer
LOGGER.error("Programmer Error: No ["+IWrapperFactory.class.getSimpleName()+"] assigned to the DhApi."); LOGGER.error("Programmer Error: No ["+IWrapperFactory.class.getSimpleName()+"] assigned to the DhApi.");
} }
// log a warning if G1GC is being used
// (this garbage collector is known to cause stuttering)
{
boolean g1GcInUse = false;
StringBuilder garbageCollectorNames = new StringBuilder();
List<GarbageCollectorMXBean> gcMxBeans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean gcMxBean : gcMxBeans)
{
if (!garbageCollectorNames.toString().isEmpty())
{
garbageCollectorNames.append(", ");
}
garbageCollectorNames.append(gcMxBean.getName());
// "G1 Young Generation" // "G1 Concurrent GC" // "G1 Old Generation"
if (gcMxBean.getName().toLowerCase().contains("g1 "))
{
g1GcInUse = true;
}
}
LOGGER.info("Garbage collectors: ["+garbageCollectorNames+"]");
if (g1GcInUse
&& Config.Common.Logging.Warning.logGarbageCollectorWarning.get())
{
LOGGER.warn(
"Distant Horizons: G1 Garbage collector detected. \n" +
"This garbage collector can cause FPS stuttering. \n" +
"It's recommended to use a concurrent garbage collector \n" +
"like ZGC (Java 21+) or Shenandoah (Java 8 through 17) for a smoother experience. \n" +
"");
}
}
} }
} }
@@ -56,6 +56,8 @@ import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import java.io.File; import java.io.File;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.*; import java.util.*;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
@@ -93,6 +95,7 @@ public class ClientApi
private boolean isDevBuildMessagePrinted = false; private boolean isDevBuildMessagePrinted = false;
private boolean lowMemoryWarningPrinted = false; private boolean lowMemoryWarningPrinted = false;
private boolean highVanillaRenderDistanceWarningPrinted = false; private boolean highVanillaRenderDistanceWarningPrinted = false;
private boolean g1GarbageCollectorWarningPrinted = false;
private long lastStaticWarningMessageSentMsTime = 0L; private long lastStaticWarningMessageSentMsTime = 0L;
@@ -318,35 +321,6 @@ public class ClientApi
//============//
// clint tick //
//============//
@Deprecated
public void clientTickEvent()
{
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
profiler.push("DH-ClientTick");
try
{
IDhClientWorld clientWorld = SharedApi.tryGetDhClientWorld();
if (clientWorld != null)
{
clientWorld.clientTick();
}
}
catch (Exception e)
{
// handle errors here to prevent blowing up a mixin or API up stream
LOGGER.error("Unexpected error in ClientApi.clientTickEvent(), error: "+e.getMessage(), e);
}
profiler.pop();
}
//============// //============//
// networking // // networking //
//============// //============//
@@ -405,8 +379,11 @@ public class ClientApi
try try
{ {
// make sure the GLProxy is created for future use
GLProxy.getInstance();
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks // these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
GLProxy.getInstance().runRenderThreadTasks(); GLProxy.runRenderThreadTasks();
} }
catch (Exception e) catch (Exception e)
{ {
@@ -674,7 +651,8 @@ public class ClientApi
{ {
// dev build // dev build
if (ModInfo.IS_DEV_BUILD if (ModInfo.IS_DEV_BUILD
&& !this.isDevBuildMessagePrinted && MC_CLIENT.playerExists()) && !this.isDevBuildMessagePrinted
&& MC_CLIENT.playerExists())
{ {
this.isDevBuildMessagePrinted = true; this.isDevBuildMessagePrinted = true;
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis(); this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
@@ -720,10 +698,11 @@ public class ClientApi
if (!this.highVanillaRenderDistanceWarningPrinted if (!this.highVanillaRenderDistanceWarningPrinted
&& Config.Common.Logging.Warning.showHighVanillaRenderDistanceWarning.get()) && Config.Common.Logging.Warning.showHighVanillaRenderDistanceWarning.get())
{ {
this.highVanillaRenderDistanceWarningPrinted = true;
// DH generally doesn't need a vanilla render distance above 12 // DH generally doesn't need a vanilla render distance above 12
if (MC_RENDER.getRenderDistance() > 12) if (MC_RENDER.getRenderDistance() > 12)
{ {
this.highVanillaRenderDistanceWarningPrinted = true;
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis(); this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
String message = String message =
@@ -90,7 +90,7 @@ public class ClientPluginChannelApi
LOGGER.info("Server level key received: [" + msg.levelKey + "]."); LOGGER.info("Server level key received: [" + msg.levelKey + "].");
GLProxy.getInstance().queueRunningOnRenderThread(() -> GLProxy.queueRunningOnRenderThread(() ->
{ {
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true); IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(); IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
@@ -33,13 +33,12 @@ import com.seibel.distanthorizons.core.config.types.enums.*;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.NativeDialogUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
import java.awt.*;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
@@ -420,6 +419,14 @@ public class Config
"") "")
.build(); .build();
public static ConfigEntry<Integer> fadeDistanceInBlocks = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0, 1_600, 30_000_000)
.comment("" +
"The distance in blocks from the camera where the SSAO will fade out to. \n"+
"This is done to prevent banding and noise at extreme distances. \n"+
"")
.build();
} }
public static class GenericRendering public static class GenericRendering
@@ -786,6 +793,11 @@ public class Config
+ "A comma separated list of block resource locations that won't be rendered by DH. \n" + "A comma separated list of block resource locations that won't be rendered by DH. \n"
+ "Air is always included in this list. \n" + "Air is always included in this list. \n"
+ "Requires a restart to change. \n" + "Requires a restart to change. \n"
+ "\n"
+ "Note:\n"
+ "If you see gaps, or holes you may have to change\n"
+ "worldCompression to ["+EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS+"] and re-generate the LODs.\n"
+ "Black spots may happen occur to block lighting being zero for covered blocks.\n"
+ "") + "")
.build(); .build();
@@ -1243,23 +1255,17 @@ public class Config
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // no GUI renderer set up currently .setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // no GUI renderer set up currently
.build(); .build();
public static ConfigUIButton uiButtonTest = new ConfigUIButton(() -> public static ConfigUIButton uiButtonTest = new ConfigUIButton(() ->
{ {
// running on a separate thread is necessary to prevent locking // running on a separate thread is necessary to prevent locking
new Thread(() -> new Thread(() -> onButtonPressed()).start();
{
if (!GraphicsEnvironment.isHeadless())
{
LOGGER.info("Attempting to show tinyfd message box...");
boolean buttonPress = TinyFileDialogs.tinyfd_messageBox("Button pressed!", "UITester dialog", "ok", "info", false);
LOGGER.info("dialog returned with ["+(buttonPress ? "TRUE" : "FALSE")+"]");
}
else
{
LOGGER.info("button pressed!");
}
}).start();
}); });
public static void onButtonPressed()
{
LOGGER.info("Attempting to show tinyfd message box...");
boolean buttonPress = NativeDialogUtil.showDialog("Button pressed!", "UITester dialog", "ok", "info");
LOGGER.info("dialog returned with ["+(buttonPress ? "TRUE" : "FALSE")+"]");
}
public static ConfigCategory categoryTest = new ConfigCategory.Builder().set(CategoryTest.class).build(); public static ConfigCategory categoryTest = new ConfigCategory.Builder().set(CategoryTest.class).build();
@@ -1661,6 +1667,15 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Boolean> logGarbageCollectorWarning = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If enabled, a message will be logged if the garbage \n"
+ "collector Java is currently using is known \n"
+ "to cause stutters and/or issues. \n"
+ "")
.build();
} }
} }
@@ -107,7 +107,7 @@ public class LodBufferContainer implements AutoCloseable
// upload on MC's render thread // upload on MC's render thread
GLProxy.getInstance().queueRunningOnRenderThread(() -> GLProxy.queueRunningOnRenderThread(() ->
{ {
try try
{ {
@@ -295,7 +295,7 @@ public class LodBufferContainer implements AutoCloseable
{ {
this.buffersUploaded = false; this.buffersUploaded = false;
GLProxy.getInstance().queueRunningOnRenderThread(() -> GLProxy.queueRunningOnRenderThread(() ->
{ {
for (GLVertexBuffer buffer : this.vbos) for (GLVertexBuffer buffer : this.vbos)
{ {
@@ -399,31 +399,33 @@ public class LodQuadBuilder
} }
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) 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;
blocklight %= 16;
bb.putShort(x); bb.putShort(x);
bb.putShort(y); bb.putShort(y);
bb.putShort(z); bb.putShort(z);
short meta = 0; short meta = 0;
meta |= (skylight | (blocklight << 4)); {
byte mirco = 0; skylight %= 16;
// mirco offset which is a xyz 2bit value blocklight %= 16;
// 0b00 = no offset meta |= (short) (skylight | (blocklight << 4));
// 0b01 = positive offset
// 0b11 = negative offset byte mircoOffset = 0;
// format is: 0b00zzyyxx // mirco offset which is a xyz 2bit value
if (mx != 0) mirco |= mx > 0 ? 0b01 : 0b11; // 0b00 = no offset
if (my != 0) mirco |= my > 0 ? 0b0100 : 0b1100; // 0b01 = positive offset
if (mz != 0) mirco |= mz > 0 ? 0b010000 : 0b110000; // 0b11 = negative offset
meta |= mirco << 8; // format is: 0b00zzyyxx
if (mx != 0) { mircoOffset |= (byte) (mx > 0 ? 0b01 : 0b11); }
if (my != 0) { mircoOffset |= (byte) (my > 0 ? 0b0100 : 0b1100); }
if (mz != 0) { mircoOffset |= (byte) (mz > 0 ? 0b010000 : 0b110000); }
meta |= (short) (mircoOffset << 8);
}
bb.putShort(meta); bb.putShort(meta);
byte r = (byte) ColorUtil.getRed(color); byte r = (byte) ColorUtil.getRed(color);
byte g = (byte) ColorUtil.getGreen(color); byte g = (byte) ColorUtil.getGreen(color);
byte b = (byte) ColorUtil.getBlue(color); byte b = (byte) ColorUtil.getBlue(color);
byte a = this.doTransparency ? (byte) ColorUtil.getAlpha(color) : (byte) 255; // TODO should this be called here or happen somewhere else? byte a = this.doTransparency ? (byte) ColorUtil.getAlpha(color) : (byte) 255;
bb.put(r); bb.put(r);
bb.put(g); bb.put(g);
bb.put(b); bb.put(b);
@@ -118,15 +118,19 @@ public class DhLightingEngine
* @param centerChunk the chunk we want to apply lighting to * @param centerChunk the chunk we want to apply lighting to
* @param nearbyChunkList should also contain centerChunk * @param nearbyChunkList should also contain centerChunk
* @param maxSkyLight should be a value between 0 and 15 * @param maxSkyLight should be a value between 0 and 15
*
* @return the number of light positions iterated over, can be used for profiling.
*/ */
private void lightChunk( private int lightChunk(
@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList, @NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList,
int maxSkyLight, boolean updateBlockLight, boolean updateSkyLight) int maxSkyLight, boolean updateBlockLight, boolean updateSkyLight)
{ {
DhChunkPos centerChunkPos = centerChunk.getChunkPos(); DhChunkPos centerChunkPos = centerChunk.getChunkPos();
AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk); AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk);
// how many positions we've walked over, can be used for profiling/debugging
int posIterations = 0;
// try-finally to handle the stableArray resources // try-finally to handle the stableArray resources
StableLightPosStack blockLightWorldPosQueue = null; StableLightPosStack blockLightWorldPosQueue = null;
StableLightPosStack skyLightWorldPosQueue = null; StableLightPosStack skyLightWorldPosQueue = null;
@@ -245,13 +249,15 @@ public class DhLightingEngine
} }
} }
// block light // block light
if (updateBlockLight) if (updateBlockLight)
{ {
// done to prevent a rare issue where the light values are incorrectly set to -1 // done to prevent a rare issue where the light values are incorrectly set to -1
centerChunk.clearDhBlockLighting(); centerChunk.clearDhBlockLighting();
this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder, posIterations += this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), (neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
true); true);
@@ -262,7 +268,7 @@ public class DhLightingEngine
{ {
centerChunk.clearDhSkyLighting(); centerChunk.clearDhSkyLighting();
this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder, posIterations += this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), (neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
false); false);
@@ -287,10 +293,12 @@ public class DhLightingEngine
{ {
centerChunk.setIsDhSkyLightCorrect(true); centerChunk.setIsDhSkyLightCorrect(true);
} }
return posIterations;
} }
/** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */ /** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */
private void propagateChunkLightPosList( private int propagateChunkLightPosList(
StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder, StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder,
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc, IGetLightFunc getLightFunc, ISetLightFunc setLightFunc,
boolean propagatingBlockLights) boolean propagatingBlockLights)
@@ -320,66 +328,89 @@ public class DhLightingEngine
IBlockStateWrapper previousBlockState = null; IBlockStateWrapper previousBlockState = null;
// update each light position int iterations = 0;
while (!lightPosQueue.isEmpty())
// update each light level
for (int currentLightLevel = LodUtil.MAX_MC_LIGHT; currentLightLevel >= LodUtil.MIN_MC_LIGHT; currentLightLevel--)
{ {
// since we don't care about the order the positions are processed, // Walking down from the top light level to the bottom can reduce iterating over
// we can grab the last position instead of the first for a slight performance increase (this way the array doesn't need to be shifted over every loop) // the same positions multiple times.
lightPosQueue.popMutate(lightPos); // At best this seems to behave at roughly 2x the speed of just blindly putting light pos
// in a queue and at worse slightly faster than the blind queue.
int lightValue = lightPos.lightValue; lightPos.lightValue = currentLightLevel;
// update each light position
// propagate the lighting in each cardinal direction, IE: -x, +x, -y, +y, -z, +z while (!lightPosQueue.isLightLevelEmpty(currentLightLevel))
for (EDhDirection direction : EDhDirection.ALL) // since this is an array instead of an ArrayList this advanced for-loop shouldn't cause any GC issues
{ {
lightPos.mutateOffset(direction, neighbourBlockPos); // since we don't care about the order the positions are processed,
neighbourBlockPos.mutateToChunkRelativePos(relNeighbourBlockPos); // we can grab the last position instead of the first for a slight performance increase (this way the array doesn't need to be shifted over every loop)
lightPosQueue.popMutate(lightPos, currentLightLevel);
iterations++;
int lightValue = lightPos.lightValue;
// only continue if the light position is inside one of our chunks // propagate the lighting in each cardinal direction, IE: -x, +x, -y, +y, -z, +z
IChunkWrapper neighbourChunk = adjacentChunkHolder.getByBlockPos(neighbourBlockPos.getX(), neighbourBlockPos.getZ()); for (EDhDirection direction : EDhDirection.ALL) // since this is an array instead of an ArrayList this advanced for-loop shouldn't cause any GC issues
if (neighbourChunk == null)
{ {
// the light pos is outside our generator's range, ignore it lightPos.mutateOffset(direction, neighbourBlockPos);
continue; neighbourBlockPos.mutateToChunkRelativePos(relNeighbourBlockPos);
}
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight()
|| relNeighbourBlockPos.getY() >= neighbourChunk.getExclusiveMaxBuildHeight())
{
// the light pos is outside the chunk's min/max height,
// this can happen if given a chunk that hasn't finished generating
continue;
}
int currentBlockLight = getLightFunc.getLight(neighbourChunk, relNeighbourBlockPos);
if (currentBlockLight >= (lightValue - 1))
{
// short circuit for when the light value at this position
// is already greater-than what we could set it
continue;
}
IBlockStateWrapper neighbourBlockState = neighbourChunk.getBlockState(relNeighbourBlockPos, mcBlockPos, previousBlockState);
previousBlockState = neighbourBlockState;
// Math.max(1, ...) is used so that the propagated light level always drops by at least 1, preventing infinite cycles.
int targetLevel = lightValue - Math.max(1, neighbourBlockState.getOpacity());
if (targetLevel > currentBlockLight)
{
// this position is darker than the new light value, update/set it
setLightFunc.setLight(neighbourChunk, relNeighbourBlockPos, targetLevel);
// now that light has been propagated to this blockPos
// we need to queue it up so its neighbours can be propagated as well // only continue if the light position is inside one of our chunks
lightPosQueue.push(neighbourBlockPos.getX(), neighbourBlockPos.getY(), neighbourBlockPos.getZ(), targetLevel); IChunkWrapper neighbourChunk = adjacentChunkHolder.getByBlockPos(neighbourBlockPos.getX(), neighbourBlockPos.getZ());
if (neighbourChunk == null)
{
// the light pos is outside our generator's range, ignore it
continue;
}
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight()
|| relNeighbourBlockPos.getY() >= neighbourChunk.getExclusiveMaxBuildHeight())
{
// the light pos is outside the chunk's min/max height,
// this can happen if given a chunk that hasn't finished generating
continue;
}
int currentBlockLight = getLightFunc.getLight(neighbourChunk, relNeighbourBlockPos);
if (currentBlockLight >= (lightValue - 1))
{
// short circuit for when the light value at this position
// is already greater-than what we could set it
continue;
}
IBlockStateWrapper neighbourBlockState = neighbourChunk.getBlockState(relNeighbourBlockPos, mcBlockPos, previousBlockState);
previousBlockState = neighbourBlockState;
// Math.max(1, ...) is used so that the propagated light level always drops by at least 1, preventing infinite cycles.
int targetLightLevel = lightValue - Math.max(1, neighbourBlockState.getOpacity());
if (targetLightLevel > currentBlockLight)
{
// this position is darker than the new light value, update/set it
setLightFunc.setLight(neighbourChunk, relNeighbourBlockPos, targetLightLevel);
// now that light has been propagated to this blockPos
// we need to queue it up so its neighbours can be propagated as well
lightPosQueue.push(neighbourBlockPos.getX(), neighbourBlockPos.getY(), neighbourBlockPos.getZ(), targetLightLevel);
}
} }
} }
} }
for (int currentLightLevel = LodUtil.MAX_MC_LIGHT; currentLightLevel >= LodUtil.MIN_MC_LIGHT; currentLightLevel--)
{
if (!lightPosQueue.isLightLevelEmpty(currentLightLevel))
{
LodUtil.assertNotReach("Non empty light pos queue for light level ["+currentLightLevel+"] after light engine running");
}
}
// can be enabled if troubleshooting lighting issues // can be enabled if troubleshooting lighting issues
if (RENDER_BLOCK_LIGHT_WIREFRAME if (RENDER_BLOCK_LIGHT_WIREFRAME
@@ -395,6 +426,7 @@ public class DhLightingEngine
// propagation complete // propagation complete
return iterations;
} }
@@ -748,16 +780,24 @@ public class DhLightingEngine
private static final Queue<StableLightPosStack> lightArrayCache = new ArrayDeque<>(); private static final Queue<StableLightPosStack> lightArrayCache = new ArrayDeque<>();
/** the index of the last item in the array, -1 if empty */ /** the index of the last item in the array, -1 if empty */
private int index = -1; private int[] indexByLightLevel = new int[LodUtil.MAX_MC_LIGHT + 1];
/** x, y, z, and lightValue. */ /** x, y, z */
public static final int INTS_PER_LIGHT_POS = 4; public static final int INTS_PER_LIGHT_POS = 3;
/** private final IntArrayList[] lightPositionsByLightLevel = new IntArrayList[LodUtil.MAX_MC_LIGHT + 1];
* When tested with a normal 1.20 world James saw a maximum of 36,709 block and 2,355 sky lights,
* so 40,000 should be a good starting point that can contain most lighting tasks.
*/ public StableLightPosStack()
private final IntArrayList lightPositions = new IntArrayList(40_000 * INTS_PER_LIGHT_POS); {
for (int i = 0; i < this.lightPositionsByLightLevel.length; i++)
{
// When tested with a normal 1.20 world James saw a maximum of 36,709 block and 2,355 sky lights,
// so 40,000 should be a good starting point that can contain most lighting tasks.
this.lightPositionsByLightLevel[i] = new IntArrayList(40_000 * INTS_PER_LIGHT_POS);
this.indexByLightLevel[i] = -1;
}
}
@@ -804,45 +844,56 @@ public class DhLightingEngine
// stack methods // // stack methods //
//===============// //===============//
public boolean isEmpty() { return this.index == -1; } public boolean isLightLevelEmpty(int lightLevel) { return this.indexByLightLevel[lightLevel] == -1; }
public int size() { return this.index+1; } //public int size() { return this.index+1; }
public void push(int blockX, int blockY, int blockZ, int lightValue) public void push(int blockX, int blockY, int blockZ, int lightLevel)
{ {
this.index++; IntArrayList lightPositions = this.lightPositionsByLightLevel[lightLevel];
int subIndex = this.index * INTS_PER_LIGHT_POS;
if (subIndex < this.lightPositions.size()) this.indexByLightLevel[lightLevel]++;
int subIndex = this.indexByLightLevel[lightLevel] * INTS_PER_LIGHT_POS;
if (subIndex < lightPositions.size())
{ {
this.lightPositions.set(subIndex, blockX); lightPositions.set(subIndex, blockX);
this.lightPositions.set(subIndex + 1, blockY); lightPositions.set(subIndex + 1, blockY);
this.lightPositions.set(subIndex + 2, blockZ); lightPositions.set(subIndex + 2, blockZ);
this.lightPositions.set(subIndex + 3, lightValue);
} }
else else
{ {
// add a new pos // add a new pos
this.lightPositions.add(blockX); lightPositions.add(blockX);
this.lightPositions.add(blockY); lightPositions.add(blockY);
this.lightPositions.add(blockZ); lightPositions.add(blockZ);
this.lightPositions.add(lightValue);
} }
} }
/** mutates the given {@link LightPos} to match the next {@link LightPos} in the queue. */ /** mutates the given {@link LightPos} to match the next {@link LightPos} in the queue. */
public void popMutate(LightPos pos) public void popMutate(LightPos pos, int lightLevel)
{ {
int subIndex = this.index * INTS_PER_LIGHT_POS; int subIndex = this.indexByLightLevel[lightLevel] * INTS_PER_LIGHT_POS;
IntArrayList lightPositions = this.lightPositionsByLightLevel[lightLevel];
pos.setX(this.lightPositions.getInt(subIndex)); pos.setX(lightPositions.getInt(subIndex));
pos.setY(this.lightPositions.getInt(subIndex + 1)); pos.setY(lightPositions.getInt(subIndex + 1));
pos.setZ(this.lightPositions.getInt(subIndex + 2)); pos.setZ(lightPositions.getInt(subIndex + 2));
pos.lightValue = this.lightPositions.getInt(subIndex + 3);
this.index--; this.indexByLightLevel[lightLevel]--;
} }
@Override @Override
public String toString() { return this.index + "/" + (this.lightPositions.size() / INTS_PER_LIGHT_POS); } public String toString()
{
StringBuilder builder = new StringBuilder();
for (int i = 0; i < this.indexByLightLevel.length; i++)
{
builder.append("light: ").append(i)
.append(" size: ").append(this.indexByLightLevel[i]).append("/").append(this.lightPositionsByLightLevel[i].size() / INTS_PER_LIGHT_POS).append("\n");
}
return builder.toString();
}
} }
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo; import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
import com.seibel.distanthorizons.core.util.NativeDialogUtil;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.jar.gui.BaseJFrame; import com.seibel.distanthorizons.core.jar.gui.BaseJFrame;
import com.seibel.distanthorizons.core.jar.gui.cusomJObject.JBox; import com.seibel.distanthorizons.core.jar.gui.cusomJObject.JBox;
@@ -31,7 +32,6 @@ import com.seibel.distanthorizons.core.jar.installer.WebDownloader;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.LoggerContext;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -441,7 +441,7 @@ public class JarMain
installMod.addActionListener(e -> { installMod.addActionListener(e -> {
if (minecraftDirPop.getSelectedFile() == null) if (minecraftDirPop.getSelectedFile() == null)
{ {
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, "Please select your install directory", "ok", "warning", false); NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, "Please select your install directory", "ok", "warning");
return; return;
} }
@@ -455,11 +455,11 @@ public class JarMain
ModInfo.NAME + "-" + ModrinthGetter.releaseNames.get(downloadID.get()) + ".jar" ModInfo.NAME + "-" + ModrinthGetter.releaseNames.get(downloadID.get()) + ".jar"
).toFile()); ).toFile());
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, "Installation done. \nYou can now close the installer", "ok", "info", false); NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, "Installation done. \nYou can now close the installer", "ok", "info");
} }
catch (Exception f) catch (Exception f)
{ {
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, "Download failed. Check your internet connection \nStacktrace: " + f.getMessage(), "error", "info", false); NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, "Download failed. Check your internet connection \nStacktrace: " + f.getMessage(), "error", "info");
} }
}); });
frame.add(installMod); frame.add(installMod);
@@ -29,12 +29,12 @@ import com.seibel.distanthorizons.core.jar.installer.ModrinthGetter;
import com.seibel.distanthorizons.core.jar.installer.WebDownloader; import com.seibel.distanthorizons.core.jar.installer.WebDownloader;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen; import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.util.NativeDialogUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants; import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.util.StringUtil; import com.seibel.distanthorizons.coreapi.util.StringUtil;
import com.seibel.distanthorizons.coreapi.util.jar.DeleteOnUnlock; import com.seibel.distanthorizons.coreapi.util.jar.DeleteOnUnlock;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -174,7 +174,7 @@ public class SelfUpdater
if (!GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).containsKey(mcVersion)) if (!GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).containsKey(mcVersion))
{ {
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Gitlab, findable versions are ["+ StringUtil.join(",", GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).keySet().toArray()) +"]."); LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Gitlab, findable versions are ["+ StringUtil.join(", ", GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).keySet().toArray()) +"].");
return false; return false;
} }
@@ -258,14 +258,13 @@ public class SelfUpdater
deleteOldJarOnJvmShutdown = true; deleteOldJarOnJvmShutdown = true;
// TODO one of these messages contains something TinyFd doesn't like, find it and fix it String successMessage = "Distant Horizons successfully updated. It will apply on game`s relaunch";
String successMessage = "Distant Horizons successfully updated. It will apply on game's relaunch";
LOGGER.info(successMessage); LOGGER.info(successMessage);
new Thread(() -> new Thread(() ->
{ {
try try
{ {
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, successMessage, "ok", "info", false); NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, successMessage, "ok", "info");
} }
catch (Exception ignore) { } catch (Exception ignore) { }
}).start(); }).start();
@@ -288,7 +287,7 @@ public class SelfUpdater
LOGGER.error(failMessage, e); LOGGER.error(failMessage, e);
try try
{ {
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, failMessage, "ok", "error", false); NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, failMessage, "ok", "error");
} }
catch (Exception ignore) { } catch (Exception ignore) { }
@@ -386,7 +385,7 @@ public class SelfUpdater
{ {
try try
{ {
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, successMessage, "ok", "info", false); NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, successMessage, "ok", "info");
} }
catch (Exception ignore) { } catch (Exception ignore) { }
}).start(); }).start();
@@ -424,7 +423,7 @@ public class SelfUpdater
LOGGER.error(failMessage, e); LOGGER.error(failMessage, e);
try try
{ {
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, failMessage, "ok", "error", false); NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, failMessage, "ok", "error");
} }
catch (Exception ignore) { } catch (Exception ignore) { }
@@ -92,7 +92,8 @@ public class NetworkSession extends AbstractNetworkEventSource
{ {
LOGGER.error("Failed to handle the message. New messages will be ignored.", e); LOGGER.error("Failed to handle the message. New messages will be ignored.", e);
LOGGER.error("Message: ["+message+"]"); LOGGER.error("Message: ["+message+"]");
this.close();
this.close(e);
} }
} }
@@ -133,6 +133,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region constructor
public LodRenderSection( public LodRenderSection(
long pos, long pos,
@@ -153,11 +154,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus); DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
} }
//endregion constructor
//======================================// //======================================//
// render data generation and uploading // // render data generation and uploading //
//======================================// //======================================//
//region render data uploading
/** @return true if the upload started, false if it wasn't able to for any reason */ /** @return true if the upload started, false if it wasn't able to for any reason */
public synchronized boolean uploadRenderDataToGpuAsync() public synchronized boolean uploadRenderDataToGpuAsync()
@@ -314,7 +318,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
} }
}); });
} }
/** async is done so each thread can run without waiting on others */ /**
* async is done so each thread can run without waiting on others
* @param direction the direction to load relative to the given position, null will return the given position
*/
private CompletableFuture<ColumnRenderSource> getRenderSourceForPosAsync(long pos, @Nullable EDhDirection direction) private CompletableFuture<ColumnRenderSource> getRenderSourceForPosAsync(long pos, @Nullable EDhDirection direction)
{ {
if (direction != null) if (direction != null)
@@ -400,11 +407,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
}); });
} }
//endregion render data uploading
//========================//
// getters and properties // //====================//
//========================// // enabling rendering //
//====================//
//region enabling rendering
public boolean canRender() { return this.bufferContainer != null; } public boolean canRender() { return this.bufferContainer != null; }
@@ -439,11 +449,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
public boolean gpuUploadInProgress() { return this.getAndBuildRenderDataFuture != null; } public boolean gpuUploadInProgress() { return this.getAndBuildRenderDataFuture != null; }
//endregion enabling rendering
//=================================// //=================================//
// full data retrieval (world gen) // // full data retrieval (world gen) //
//=================================// //=================================//
//region full data retrieval
public boolean isFullyGenerated() public boolean isFullyGenerated()
{ {
@@ -551,11 +564,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
} }
} }
//endregion full data retrieval
//=================// //=================//
// beacon handling // // beacon handling //
//=================// //=================//
//region beacon handling
/** gets the active beacon list and stops/starts beacon rendering as necessary */ /** gets the active beacon list and stops/starts beacon rendering as necessary */
private void getAndRefreshRenderingBeacons() private void getAndRefreshRenderingBeacons()
@@ -629,11 +645,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
} }
} }
//endregion beacon handling
//==============// //==============//
// base methods // // base methods //
//==============// //==============//
//region base methods
@Override @Override
public void debugRender(DebugRenderer debugRenderer) public void debugRender(DebugRenderer debugRenderer)
@@ -720,6 +739,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
} }
//endregion base methods
} }
@@ -56,13 +56,13 @@ public class GLProxy
public static final Set<String> LOGGED_GL_MESSAGES = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>()); public static final Set<String> LOGGED_GL_MESSAGES = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
private static final ConcurrentLinkedQueue<Runnable> RENDER_THREAD_RUNNABLE_QUEUE = new ConcurrentLinkedQueue<>();
private static GLProxy instance = null; private static GLProxy instance = null;
private final ConcurrentLinkedQueue<Runnable> renderThreadRunnableQueue = new ConcurrentLinkedQueue<>();
/** Minecraft's GL capabilities */ /** Minecraft's GL capabilities */
public final GLCapabilities glCapabilities; public final GLCapabilities glCapabilities;
@@ -231,7 +231,7 @@ public class GLProxy
return uploadOverride; return uploadOverride;
} }
public boolean runningOnRenderThread() public static boolean runningOnRenderThread()
{ {
long currentContext = GLFW.glfwGetCurrentContext(); long currentContext = GLFW.glfwGetCurrentContext();
return currentContext != 0L; // if the context isn't null, it's the MC context return currentContext != 0L; // if the context isn't null, it's the MC context
@@ -243,12 +243,12 @@ public class GLProxy
// Worker Thread Runnables // // Worker Thread Runnables //
//=========================// //=========================//
public void queueRunningOnRenderThread(Runnable renderCall) public static void queueRunningOnRenderThread(Runnable renderCall)
{ {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
this.renderThreadRunnableQueue.add(() -> this.runOpenGlCall(renderCall, stackTrace)); RENDER_THREAD_RUNNABLE_QUEUE.add(() -> runOpenGlCall(renderCall, stackTrace));
} }
private void runOpenGlCall(Runnable renderCall, StackTraceElement[] stackTrace) private static void runOpenGlCall(Runnable renderCall, StackTraceElement[] stackTrace)
{ {
try try
{ {
@@ -266,11 +266,11 @@ public class GLProxy
* Doesn't do any thread/GL Context validation. * Doesn't do any thread/GL Context validation.
* Running this outside of the render thread may cause crashes or other issues. * Running this outside of the render thread may cause crashes or other issues.
*/ */
public void runRenderThreadTasks() public static void runRenderThreadTasks()
{ {
long startTime = System.nanoTime(); long startTime = System.nanoTime();
Runnable runnable = this.renderThreadRunnableQueue.poll(); Runnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
while(runnable != null) while(runnable != null)
{ {
runnable.run(); runnable.run();
@@ -283,7 +283,7 @@ public class GLProxy
break; break;
} }
runnable = this.renderThreadRunnableQueue.poll(); runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
} }
} }
@@ -100,7 +100,7 @@ public class GLBuffer implements AutoCloseable
protected void create(boolean asBufferStorage) protected void create(boolean asBufferStorage)
{ {
if (!GLProxy.getInstance().runningOnRenderThread()) if (!GLProxy.runningOnRenderThread())
{ {
LodUtil.assertNotReach("Thread ["+Thread.currentThread()+"] tried to create a GLBuffer outside the MC render thread."); LodUtil.assertNotReach("Thread ["+Thread.currentThread()+"] tried to create a GLBuffer outside the MC render thread.");
} }
@@ -151,7 +151,7 @@ public class GLBuffer implements AutoCloseable
BUFFER_ID_TO_PHANTOM.remove(id); BUFFER_ID_TO_PHANTOM.remove(id);
} }
GLProxy.getInstance().queueRunningOnRenderThread(() -> GLProxy.queueRunningOnRenderThread(() ->
{ {
// destroy the buffer if it exists, // destroy the buffer if it exists,
// the buffer may not exist if the destroy method is called twice // the buffer may not exist if the destroy method is called twice
@@ -44,7 +44,7 @@ public class QuadElementBuffer extends GLElementBuffer
public int getCapacity() public int getCapacity()
{ {
return super.getSize() / GLEnums.getTypeSize(getType()); return super.getSize() / GLEnums.getTypeSize(this.getType());
} }
private static void buildBufferByte(int quadCount, ByteBuffer buffer) private static void buildBufferByte(int quadCount, ByteBuffer buffer)
@@ -140,7 +140,6 @@ public class QuadElementBuffer extends GLElementBuffer
return; return;
} }
int vertexCount = quadCount * 4; // 4 vertices per quad int vertexCount = quadCount * 4; // 4 vertices per quad
GLProxy gl = GLProxy.getInstance();
if (vertexCount < 255) if (vertexCount < 255)
{ // Reserve 1 for the reset index { // Reserve 1 for the reset index
@@ -158,7 +157,7 @@ public class QuadElementBuffer extends GLElementBuffer
ByteBuffer buffer = MemoryUtil.memAlloc(this.indicesCount * GLEnums.getTypeSize(this.type)); ByteBuffer buffer = MemoryUtil.memAlloc(this.indicesCount * GLEnums.getTypeSize(this.type));
buildBuffer(quadCount, buffer, this.type); buildBuffer(quadCount, buffer, this.type);
if (!gl.bufferStorageSupported) if (!GLProxy.getInstance().bufferStorageSupported)
{ {
this.bind(); this.bind();
@@ -95,7 +95,7 @@ public class ShaderProgram
for (int i = 0; i < attributes.length; i++) for (int i = 0; i < attributes.length; i++)
{ {
GL32.glBindAttribLocation(id, i, attributes[i]); GL32.glBindAttribLocation(this.id, i, attributes[i]);
} }
GL32.glLinkProgram(this.id); GL32.glLinkProgram(this.id);
@@ -59,8 +59,10 @@ public final class VertexPointer
/** Always aligned to 4 bytes */ /** Always aligned to 4 bytes */
public static VertexPointer addUnsignedBytePointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_UNSIGNED_BYTE, normalized, 4, useInteger); } public static VertexPointer addUnsignedBytePointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_UNSIGNED_BYTE, normalized, 4, useInteger); }
/** aligned to 4 bytes */ /** aligned to 4 bytes */
public static VertexPointer addUnsignedBytesPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_UNSIGNED_BYTE, normalized, _align(elementCount), useInteger); } public static VertexPointer addUnsignedBytesPointer(int elementCount, boolean normalized, boolean useInteger)
public static VertexPointer addUnsignedShortsPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_UNSIGNED_SHORT, normalized, _align(elementCount * 2), useInteger); } { return new VertexPointer(elementCount, GL32.GL_UNSIGNED_BYTE, normalized, _align(elementCount), useInteger); }
public static VertexPointer addUnsignedShortsPointer(int elementCount, boolean normalized, boolean useInteger)
{ return new VertexPointer(elementCount, GL32.GL_UNSIGNED_SHORT, normalized, _align(elementCount * 2), useInteger); }
public static VertexPointer addShortsPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_SHORT, normalized, _align(elementCount * 2), useInteger); } public static VertexPointer addShortsPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_SHORT, normalized, _align(elementCount * 2), useInteger); }
public static VertexPointer addIntPointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_INT, normalized, 4, useInteger); } public static VertexPointer addIntPointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_INT, normalized, 4, useInteger); }
public static VertexPointer addIVec2Pointer(boolean normalized, boolean useInteger) { return new VertexPointer(2, GL32.GL_INT, normalized, 8, useInteger); } public static VertexPointer addIVec2Pointer(boolean normalized, boolean useInteger) { return new VertexPointer(2, GL32.GL_INT, normalized, 8, useInteger); }
@@ -96,16 +96,19 @@ public class DhFadeRenderer
} }
this.fadeTexture = GL32.glGenTextures(); this.fadeTexture = GL32.glGenTextures();
GLMC.glBindTexture(this.fadeTexture); {
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null); GLMC.glBindTexture(this.fadeTexture);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR); GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR); GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
// disable mip-mapping since DH is just going to draw straight to the screen
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0); // disable mip-mapping since DH is just going to draw straight to the screen
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0); GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
}
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fadeTexture, 0); GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fadeTexture, 0);
} }
@@ -64,7 +64,7 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
public int uNoiseDropoff = -1; public int uNoiseDropoff = -1;
// Debug Uniform // Debug Uniform
public int uWhiteWorld = -1; public int uIsWhiteWorld = -1;
@@ -102,7 +102,7 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
this.uNoiseDropoff = this.getUniformLocation("uNoiseDropoff"); this.uNoiseDropoff = this.getUniformLocation("uNoiseDropoff");
// Debug Uniform // Debug Uniform
this.uWhiteWorld = this.getUniformLocation("uWhiteWorld"); this.uIsWhiteWorld = this.getUniformLocation("uIsWhiteWorld");
// TODO: Add better use of the LODFormat thing // TODO: Add better use of the LODFormat thing
@@ -192,7 +192,7 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
this.setUniform(this.uNoiseDropoff, Config.Client.Advanced.Graphics.NoiseTexture.noiseDropoff.get()); this.setUniform(this.uNoiseDropoff, Config.Client.Advanced.Graphics.NoiseTexture.noiseDropoff.get());
// Debug // Debug
this.setUniform(this.uWhiteWorld, Config.Client.Advanced.Debugging.enableWhiteWorld.get()); this.setUniform(this.uIsWhiteWorld, Config.Client.Advanced.Debugging.enableWhiteWorld.get());
// Clip Uniform // Clip Uniform
float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocksForFading(renderParameters.partialTicks); float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocksForFading(renderParameters.partialTicks);
@@ -88,15 +88,17 @@ public class FogRenderer
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fogFramebuffer); GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fogFramebuffer);
this.fogTexture = GLMC.glGenTextures(); this.fogTexture = GLMC.glGenTextures();
GLMC.glBindTexture(this.fogTexture); {
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null); GLMC.glBindTexture(this.fogTexture);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR); GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR); GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fogTexture, 0); GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fogTexture, 0);
// disable mip-mapping since DH is just going to draw straight to the screen
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0); // disable mip-mapping since DH is just going to draw straight to the screen
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0); GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
}
} }
@@ -493,12 +493,8 @@ public class LodRenderer
return false; return false;
} }
if (!GLProxy.hasInstance()) // GLProxy should have already been created by this point, but just in case create it now
{ GLProxy.getInstance();
// shouldn't normally happen, but just in case
LOGGER.warn("Renderer setup called but GLProxy has not yet been setup!");
return false;
}
@@ -88,14 +88,16 @@ public class SSAORenderer
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.ssaoFramebuffer); GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.ssaoFramebuffer);
this.ssaoTexture = GLMC.glGenTextures(); this.ssaoTexture = GLMC.glGenTextures();
GLMC.glBindTexture(this.ssaoTexture); {
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_R16F, width, height, 0, GL32.GL_RED, GL32.GL_HALF_FLOAT, (ByteBuffer) null); GLMC.glBindTexture(this.ssaoTexture);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR); GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_R16F, width, height, 0, GL32.GL_RED, GL32.GL_HALF_FLOAT, (ByteBuffer) null);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR); GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
// disable mip-mapping since DH is just going to draw straight to the screen
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0); // disable mip-mapping since DH is just going to draw straight to the screen
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0); GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
}
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.ssaoTexture, 0); GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.ssaoTexture, 0);
} }
@@ -337,7 +337,7 @@ public class RenderableBoxGroup
@Override @Override
public void close() public void close()
{ {
GLProxy.getInstance().queueRunningOnRenderThread(() -> GLProxy.queueRunningOnRenderThread(() ->
{ {
if (this.instanceChunkPosVbo != 0) if (this.instanceChunkPosVbo != 0)
{ {
@@ -178,6 +178,14 @@ public class FogShader extends AbstractShaderRenderer
float farFogMax = Config.Client.Advanced.Graphics.Fog.farFogMax.get().floatValue(); float farFogMax = Config.Client.Advanced.Graphics.Fog.farFogMax.get().floatValue();
float farFogDensity = Config.Client.Advanced.Graphics.Fog.farFogDensity.get().floatValue(); float farFogDensity = Config.Client.Advanced.Graphics.Fog.farFogDensity.get().floatValue();
// override fog if underwater
if (MC_RENDER.isFogStateSpecial())
{
// hide everything behind fog
farFogStart = 0.0f;
farFogEnd = 0.0f;
}
this.shader.setUniform(this.uFarFogStart, farFogStart); this.shader.setUniform(this.uFarFogStart, farFogStart);
this.shader.setUniform(this.uFarFogLength, farFogEnd - farFogStart); this.shader.setUniform(this.uFarFogLength, farFogEnd - farFogStart);
this.shader.setUniform(this.uFarFogMin, farFogMin); this.shader.setUniform(this.uFarFogMin, farFogMin);
@@ -25,8 +25,10 @@ import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer; import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.render.renderer.SSAORenderer; import com.seibel.distanthorizons.core.render.renderer.SSAORenderer;
import com.seibel.distanthorizons.core.render.renderer.ScreenQuad; import com.seibel.distanthorizons.core.render.renderer.ScreenQuad;
import com.seibel.distanthorizons.core.util.NumberUtil;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL32;
/** /**
@@ -58,6 +60,7 @@ public class SSAOShader extends AbstractShaderRenderer
public int uMinLight; public int uMinLight;
public int uBias; public int uBias;
public int uDepthMap; public int uDepthMap;
public int uFadeDistanceInBlocks;
@@ -81,6 +84,7 @@ public class SSAOShader extends AbstractShaderRenderer
this.uMinLight = this.shader.getUniformLocation("uMinLight"); this.uMinLight = this.shader.getUniformLocation("uMinLight");
this.uBias = this.shader.getUniformLocation("uBias"); this.uBias = this.shader.getUniformLocation("uBias");
this.uDepthMap = this.shader.getUniformLocation("uDepthMap"); this.uDepthMap = this.shader.getUniformLocation("uDepthMap");
this.uFadeDistanceInBlocks = this.shader.getUniformLocation("uFadeDistanceInBlocks");
} }
@@ -120,6 +124,10 @@ public class SSAOShader extends AbstractShaderRenderer
this.shader.setUniform(this.uBias, bias.floatValue()); this.shader.setUniform(this.uBias, bias.floatValue());
GL32.glUniform1i(this.uDepthMap, 0); GL32.glUniform1i(this.uDepthMap, 0);
float fadeDistanceInBlocks = Config.Client.Advanced.Graphics.Ssao.fadeDistanceInBlocks.get().floatValue();
fadeDistanceInBlocks = MathUtil.clamp(0.0f, fadeDistanceInBlocks, Float.MAX_VALUE); // clamp to prevent accidentally setting a negative number
this.shader.setUniform(this.uFadeDistanceInBlocks, fadeDistanceInBlocks);
} }
@@ -0,0 +1,30 @@
package com.seibel.distanthorizons.core.util;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
/**
* Should be used instead of the direct call to {@link TinyFileDialogs}
* so we can run additional validation and/or string cleanup.
* Otherwise, we may get error messages back.
*
* @see TinyFileDialogs
*/
public class NativeDialogUtil
{
/**
* @param dialogType the dialog type. One of:<br><table><tr><td>"ok"</td><td>"okcancel"</td><td>"yesno"</td><td>"yesnocancel"</td></tr></table>
* @param iconType the icon type. One of:<br><table><tr><td>"info"</td><td>"warning"</td><td>"error"</td><td>"question"</td></tr></table>
*/
public static boolean showDialog(String title, String message, String dialogType, String iconType)
{
// Tinyfd doesn't support the following characters, attempting to display them will cause the message
// to be replaced with an error message
String unsafeCharsRegex = "['\"`]";
title = title.replaceAll(unsafeCharsRegex, "`");
message = message.replaceAll(unsafeCharsRegex, "`");
return TinyFileDialogs.tinyfd_messageBox(title, message, dialogType, iconType, false);
}
}
@@ -1,102 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 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.objects;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
public class EventLoop implements AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private final boolean PAUSE_ON_ERROR = ModInfo.IS_DEV_BUILD;
private final ExecutorService executorService;
private final Runnable runnable;
/** the future related to the given runnable */
private CompletableFuture<Void> runnableFuture;
private boolean isRunning = true;
public EventLoop(ExecutorService executorService, Runnable runnable)
{
this.executorService = executorService;
this.runnable = runnable;
}
public void tick()
{
if (runnableFuture != null && runnableFuture.isDone())
{
try
{
runnableFuture.join();
}
catch (CompletionException ce)
{
LOGGER.error("Uncaught exception in event loop", ce.getCause());
if (PAUSE_ON_ERROR)
{
isRunning = false;
}
}
catch (Exception e)
{
LOGGER.error("Exception in event loop", e);
if (PAUSE_ON_ERROR)
{
isRunning = false;
}
}
finally
{
runnableFuture = null;
}
}
if (runnableFuture == null && isRunning)
{
runnableFuture = CompletableFuture.runAsync(runnable, executorService);
}
}
public void close()
{
if (runnableFuture != null)
{
runnableFuture.cancel(true);
}
runnableFuture = null;
executorService.shutdown();
}
public boolean isRunning() { return runnableFuture != null && !runnableFuture.isDone(); }
}
@@ -21,27 +21,21 @@ package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.level.DhClientServerLevel; import com.seibel.distanthorizons.core.level.DhClientServerLevel;
import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.EventLoop;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLevel> implements IDhClientWorld public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLevel> implements IDhClientWorld
{ {
private final Set<DhClientServerLevel> dhLevels = Collections.synchronizedSet(new HashSet<>()); private final Set<DhClientServerLevel> dhLevels = Collections.synchronizedSet(new HashSet<>());
public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client Server World Ticker", 2); private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop
@@ -53,6 +47,15 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
{ {
super(EWorldEnvironment.CLIENT_SERVER); super(EWorldEnvironment.CLIENT_SERVER);
LOGGER.info("Started DhWorld of type " + this.environment); LOGGER.info("Started DhWorld of type " + this.environment);
this.clientTickTimer.scheduleAtFixedRate(new TimerTask()
{
@Override
public void run()
{
DhClientServerWorld.this.dhLevels.forEach(DhClientServerLevel::clientTick);
}
}, 0, IDhClientWorld.TICK_RATE_IN_MS);
} }
@@ -136,19 +139,6 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
} }
} }
private void _clientTick()
{
//LOGGER.info("Client world tick with {} levels", levels.size());
this.dhLevels.forEach(DhClientServerLevel::clientTick);
}
@Override
public void clientTick()
{
//LOGGER.info("Client world tick");
this.eventLoop.tick();
}
//================// //================//
@@ -194,8 +184,8 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
this.dhLevelByLevelWrapper.clear(); this.dhLevelByLevelWrapper.clear();
this.eventLoop.close(); this.clientTickTimer.cancel();
LOGGER.info("Closed DhWorld of type " + this.environment); LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
} }
} }
@@ -24,17 +24,17 @@ import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.level.DhClientLevel; import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState; import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.EventLoop;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
{ {
@@ -42,8 +42,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
public final ClientOnlySaveStructure saveStructure; public final ClientOnlySaveStructure saveStructure;
public final ClientNetworkState networkState = new ClientNetworkState(); public final ClientNetworkState networkState = new ClientNetworkState();
public final ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client World Ticker"); private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
public final EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick);
@@ -59,6 +58,15 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
this.levels = new ConcurrentHashMap<>(); this.levels = new ConcurrentHashMap<>();
LOGGER.info("Started DhWorld of type " + this.environment); LOGGER.info("Started DhWorld of type " + this.environment);
this.clientTickTimer.scheduleAtFixedRate(new TimerTask()
{
@Override
public void run()
{
DhClientWorld.this.levels.values().forEach(DhClientLevel::clientTick);
}
}, 0, IDhClientWorld.TICK_RATE_IN_MS);
} }
@@ -127,11 +135,6 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
} }
} }
private void _clientTick() { this.levels.values().forEach(DhClientLevel::clientTick); }
@Override
public void clientTick() { this.eventLoop.tick(); }
@Override @Override
public void addDebugMenuStringsToList(List<String> messageList) public void addDebugMenuStringsToList(List<String> messageList)
{ {
@@ -143,7 +146,6 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
public void close() public void close()
{ {
this.networkState.close(); this.networkState.close();
this.dhTickerThread.shutdownNow();
ArrayList<CompletableFuture<Void>> closeFutures = new ArrayList<>(); ArrayList<CompletableFuture<Void>> closeFutures = new ArrayList<>();
for (DhClientLevel dhClientLevel : this.levels.values()) for (DhClientLevel dhClientLevel : this.levels.values())
@@ -175,7 +177,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
} }
this.levels.clear(); this.levels.clear();
this.eventLoop.close(); this.clientTickTimer.cancel();
LOGGER.info("Closed DhWorld of type [" + this.environment + "]."); LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
} }
@@ -24,7 +24,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
public interface IDhClientWorld extends IDhWorld public interface IDhClientWorld extends IDhWorld
{ {
void clientTick(); /** how long in between client ticks in milliseconds */
long TICK_RATE_IN_MS = 100L;
default IDhClientLevel getOrLoadClientLevel(ILevelWrapper levelWrapper) { return (IDhClientLevel) this.getOrLoadLevel(levelWrapper); } default IDhClientLevel getOrLoadClientLevel(ILevelWrapper levelWrapper) { return (IDhClientLevel) this.getOrLoadLevel(levelWrapper); }
default IDhClientLevel getClientLevel(ILevelWrapper levelWrapper) { return (IDhClientLevel) this.getLevel(levelWrapper); } default IDhClientLevel getClientLevel(ILevelWrapper levelWrapper) { return (IDhClientLevel) this.getLevel(levelWrapper); }
@@ -126,8 +126,6 @@ public interface IChunkWrapper extends IBindable
IBiomeWrapper getBiome(int relX, int relY, int relZ); IBiomeWrapper getBiome(int relX, int relY, int relZ);
boolean isStillValid();
//========================// //========================//
@@ -52,6 +52,10 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
@Override @Override
IDimensionTypeWrapper getDimensionType(); IDimensionTypeWrapper getDimensionType();
/**
* Includes both the namespace and name. <br>
* example: "minecraft:overworld"
*/
@Override @Override
String getDimensionName(); String getDimensionName();
@@ -208,6 +208,10 @@
"Blur Radius", "Blur Radius",
"distanthorizons.config.client.advanced.graphics.ssao.blurRadius.@tooltip": "distanthorizons.config.client.advanced.graphics.ssao.blurRadius.@tooltip":
"The radius, measured in pixels, that blurring is calculated for the SSAO. \nHigher numbers will reduce banding at the cost of GPU performance.", "The radius, measured in pixels, that blurring is calculated for the SSAO. \nHigher numbers will reduce banding at the cost of GPU performance.",
"distanthorizons.config.client.advanced.graphics.ssao.fadeDistanceInBlocks":
"Fade Distance",
"distanthorizons.config.client.advanced.graphics.ssao.fadeDistanceInBlocks.@tooltip":
"The distance in blocks from the camera where the SSAO will fade out to. \nThis is done to prevent banding and noise at extreme distances.",
@@ -733,6 +737,8 @@
"Show Slow World Gen Warnings", "Show Slow World Gen Warnings",
"distanthorizons.config.common.logging.warning.showModCompatibilityWarningsOnStartup": "distanthorizons.config.common.logging.warning.showModCompatibilityWarningsOnStartup":
"Show Mod Compatibility Warnings", "Show Mod Compatibility Warnings",
"distanthorizons.config.common.logging.warning.logGarbageCollectorWarning":
"Log Garbage Collector Warning",
+24 -9
View File
@@ -17,6 +17,7 @@ uniform float uMinLight;
uniform float uBias; uniform float uBias;
uniform mat4 uInvProj; uniform mat4 uInvProj;
uniform mat4 uProj; uniform mat4 uProj;
uniform float uFadeDistanceInBlocks;
const float EPSILON = 1.e-6; const float EPSILON = 1.e-6;
const float GOLDEN_ANGLE = 2.39996323; const float GOLDEN_ANGLE = 2.39996323;
@@ -99,16 +100,30 @@ void main()
{ {
vec3 viewPos = calcViewPosition(vec3(TexCoord, fragmentDepth)); vec3 viewPos = calcViewPosition(vec3(TexCoord, fragmentDepth));
#ifdef GL_ARB_derivative_control // fading is done to prevent banding/noise
// Get higher precision derivatives when available // at super far distance
vec3 viewNormal = cross(dFdxFine(viewPos.xyz), dFdyFine(viewPos.xyz)); float distanceFromCamera = length(viewPos);
#else float fadeDistance = uFadeDistanceInBlocks;
vec3 viewNormal = cross(dFdx(viewPos.xyz), dFdy(viewPos.xyz)); if (distanceFromCamera < fadeDistance)
#endif {
#ifdef GL_ARB_derivative_control
// Get higher precision derivatives when available
vec3 viewNormal = cross(dFdxFine(viewPos.xyz), dFdyFine(viewPos.xyz));
#else
vec3 viewNormal = cross(dFdx(viewPos.xyz), dFdy(viewPos.xyz));
#endif
viewNormal = normalize(viewNormal); viewNormal = normalize(viewNormal);
occlusion = GetSpiralOcclusion(TexCoord, viewPos, viewNormal);
occlusion = GetSpiralOcclusion(TexCoord, viewPos, viewNormal);
// linearly fade with distance
occlusion *= (fadeDistance - distanceFromCamera) / fadeDistance;
}
else
{
// we're out of range, no need to do any SSAO calculations
occlusion = 0.0;
}
} }
fragColor = vec4(vec3(1.0 - occlusion), 1.0); fragColor = vec4(vec3(1.0 - occlusion), 1.0);
@@ -8,13 +8,12 @@ out vec4 vertexColor;
out vec3 vertexWorldPos; out vec3 vertexWorldPos;
out float vertexYPos; out float vertexYPos;
uniform bool uWhiteWorld; uniform bool uIsWhiteWorld;
uniform mat4 uCombinedMatrix; uniform mat4 uCombinedMatrix;
uniform vec3 uModelOffset; uniform vec3 uModelOffset;
uniform float uWorldYOffset; uniform float uWorldYOffset;
uniform int uWorldSkyLight;
uniform sampler2D uLightMap; uniform sampler2D uLightMap;
uniform float uMircoOffset; uniform float uMircoOffset;
@@ -57,7 +56,7 @@ void main()
float light = (float(lights/16u)+0.5) / 16.0; float light = (float(lights/16u)+0.5) / 16.0;
vertexColor = vec4(texture(uLightMap, vec2(light, light2)).xyz, 1.0); vertexColor = vec4(texture(uLightMap, vec2(light, light2)).xyz, 1.0);
if (!uWhiteWorld) if (!uIsWhiteWorld)
{ {
vertexColor *= color; vertexColor *= color;
} }