This commit is contained in:
s809
2023-12-06 21:09:19 +05:00
42 changed files with 1640 additions and 218 deletions
@@ -24,9 +24,14 @@ package com.seibel.distanthorizons.api.enums.config;
* LIGHT <br>
* MEDIUM <br>
* HEAVY <br>
*
* CUSTOM <br>
*
* @since API 1.0.0
* @deprecated will be removed when DH updates to MC 1.21 <br>
* After removal a float value will be used to control overdraw instead.
*/
@Deprecated
public enum EOverdrawPrevention
{
// Reminder:
@@ -36,6 +41,11 @@ public enum EOverdrawPrevention
NONE,
LIGHT,
MEDIUM,
HEAVY;
HEAVY,
/**
* Should not be passed in. <br>
* Is returned if the overdraw value doesn't match any of the enums defined here.
*/
CUSTOM;
}
@@ -53,8 +53,10 @@ public interface IDhApiConfigValue<T>
* Sets the config's value. <br>
* If the newValue is set to null then the config
* will revert to using the True Value.<br>
* If the config cannot be set via the API this method will return false.
*
* If the config cannot be set via the API this method will return false. <br><br>
*
* To unset the config's value pass in Null. <br>
*
* @return true if the value was set, false otherwise.
*/
boolean setValue(T newValue);
@@ -113,13 +113,30 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
//===========================//
/**
* If enabled the near clip plane is extended to reduce
* overdraw and improve Z-fighting at extreme render distances. <br>
* Sets the distance used by the near clip plane to reduce
* overdraw. <br>
* Disabling this reduces holes in the world due to the near clip plane
* being too close to the camera and the terrain not being covered by vanilla terrain.
*
* @deprecated Use {@link IDhApiGraphicsConfig#overdrawPreventionRadius()} instead.
*/
@Deprecated
IDhApiConfigValue<EOverdrawPrevention> overdrawPrevention();
/**
* Sets the radius used by the near clip shader to reduce
* overdraw. <br>
* Measured in percentages of the render distance, IE: <br>
* 0.5 = 50% vanilla render distance <br>
* 0.1 = 10% vanilla render distance <br>
* <br>
* Setting this to 0 will reduce/prevent holes in the world due to clipping to close to the camera
* but may cause overdraw issues with transparent or non-full blocks.
*
* @since API 1.1.0
*/
IDhApiConfigValue<Double> overdrawPreventionRadius();
/**
* Modifies how bright fake chunks are. <br>
* This is done when generating the vertex data and is applied before any shaders.
@@ -34,14 +34,14 @@ public final class ModInfo
public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.0.1-a-dev";
public static final String VERSION = "2.0.2-a-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */
public static boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
/** This version should only be updated when breaking changes are introduced to the DH API */
public static final int API_MAJOR_VERSION = 1;
/** This version should be updated whenever new methods are added to the DH API */
public static final int API_MINOR_VERSION = 0;
public static final int API_MINOR_VERSION = 1;
/** This version should be updated whenever non-breaking fixes are added to the DH API */
public static final int API_PATH_VERSION = 0;
@@ -109,9 +109,14 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
// public IDhApiConfigValue<Boolean> getDisableDirectionalCulling()
// { return new DhApiConfigValue<Boolean, Boolean>(AdvancedGraphics.disableDirectionalCulling); }
@Deprecated
@Override
public IDhApiConfigValue<EOverdrawPrevention> overdrawPrevention()
{ return new DhApiConfigValue<EOverdrawPrevention, EOverdrawPrevention>(Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention); }
{ return new DhApiConfigValue<EOverdrawPrevention, EOverdrawPrevention>(Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPreventionPreset); }
@Override
public IDhApiConfigValue<Double> overdrawPreventionRadius()
{ return new DhApiConfigValue<Double, Double>(Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention); }
@Override
public IDhApiConfigValue<Double> brightnessMultiplier()
@@ -49,6 +49,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
import io.netty.buffer.ByteBuf;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import java.nio.ByteBuffer;
@@ -161,46 +162,73 @@ public class ClientApi
// level events //
//==============//
public void clientLevelUnloadEvent(IClientLevelWrapper level)
public void clientLevelUnloadEvent(@Nullable IClientLevelWrapper level)
{
LOGGER.info("Unloading client level [" + level + "].");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
try
{
world.unloadLevel(level);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
if (level == null)
{
// can happen on certain multiverse servers
return;
}
LOGGER.info("Unloading client level [" + level + "].");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
{
world.unloadLevel(level);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
}
else
{
this.waitingClientLevels.remove(level);
}
}
else
catch (Exception e)
{
this.waitingClientLevels.remove(level);
// handle errors here to prevent blowing up a mixin or API up stream
LOGGER.error("Unexpected error in ClientApi.clientLevelUnloadEvent(), error: "+e.getMessage(), e);
}
}
public void clientLevelLoadEvent(IClientLevelWrapper level) { this.clientLevelLoadEvent(level, false); }
public void multiverseClientLevelLoadEvent(IClientLevelWrapper level) { this.clientLevelLoadEvent(level, true); }
private void clientLevelLoadEvent(IClientLevelWrapper level, boolean isServerCommunication)
public void clientLevelLoadEvent(@Nullable IClientLevelWrapper level) { this.clientLevelLoadEvent(level, false); }
public void multiverseClientLevelLoadEvent(@Nullable IClientLevelWrapper level) { this.clientLevelLoadEvent(level, true); }
private void clientLevelLoadEvent(@Nullable IClientLevelWrapper level, boolean isServerCommunication)
{
if (this.isServerCommunicationEnabled && !isServerCommunication)
try
{
LOGGER.info("Server supports communication, deferring loading.");
return;
}
LOGGER.info("Loading " + (isServerCommunication ? "Multiverse" : "") + " client level [" + level + "].");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
{
world.getOrLoadLevel(level);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level));
if (this.isServerCommunicationEnabled && !isServerCommunication)
{
LOGGER.info("Server supports communication, deferring loading.");
return;
}
if (level == null)
{
// can happen on certain multiverse servers
return;
}
this.loadWaitingChunksForLevel(level);
LOGGER.info("Loading " + (isServerCommunication ? "Multiverse" : "") + " client level [" + level + "].");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
{
world.getOrLoadLevel(level);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level));
this.loadWaitingChunksForLevel(level);
}
else
{
this.waitingClientLevels.add(level);
}
}
else
catch (Exception e)
{
this.waitingClientLevels.add(level);
// handle errors here to prevent blowing up a mixin or API up stream
LOGGER.error("Unexpected error in ClientApi.clientLevelLoadEvent(), error: "+e.getMessage(), e);
}
}
private void loadWaitingChunksForLevel(IClientLevelWrapper level)
@@ -258,26 +286,35 @@ public class ClientApi
IProfilerWrapper profiler = MC.getProfiler();
profiler.push("DH-ClientTick");
boolean doFlush = System.nanoTime() - this.lastFlushNanoTime >= SPAM_LOGGER_FLUSH_NS;
if (doFlush)
try
{
this.lastFlushNanoTime = System.nanoTime();
SpamReducedLogger.flushAll();
}
ConfigBasedLogger.updateAll();
ConfigBasedSpamLogger.updateAll(doFlush);
IDhClientWorld clientWorld = SharedApi.getIDhClientWorld();
if (clientWorld != null)
{
clientWorld.clientTick();
// Ignore local world gen, as it's managed by server ticking
if (!(clientWorld instanceof DhClientServerWorld))
boolean doFlush = System.nanoTime() - this.lastFlushNanoTime >= SPAM_LOGGER_FLUSH_NS;
if (doFlush)
{
SharedApi.worldGenTick(clientWorld::doWorldGen);
this.lastFlushNanoTime = System.nanoTime();
SpamReducedLogger.flushAll();
}
ConfigBasedLogger.updateAll();
ConfigBasedSpamLogger.updateAll(doFlush);
IDhClientWorld clientWorld = SharedApi.getIDhClientWorld();
if (clientWorld != null)
{
clientWorld.clientTick();
// Ignore local world gen, as it's managed by server ticking
if (!(clientWorld instanceof DhClientServerWorld))
{
SharedApi.worldGenTick(clientWorld::doWorldGen);
}
}
}
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();
}
@@ -52,6 +52,8 @@ public class SharedApi
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final Set<DhChunkPos> UPDATING_CHUNK_SET = ConcurrentHashMap.newKeySet();
private static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD = 100;
private static AbstractDhWorld currentWorld;
@@ -226,6 +228,13 @@ public class SharedApi
}
private static void bakeChunkLightingAndSendToLevelAsync(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighbourChunkList, IDhLevel dhLevel)
{
if (UPDATING_CHUNK_SET.size() >= MAX_UPDATING_CHUNK_COUNT_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads.get())
{
// The maximum number of chunks are already queued, don't add more.
// This is done to prevent overloading the system if the user flys extremely fast and queues too many chunks
return;
}
// prevent duplicate update requests
if (UPDATING_CHUNK_SET.contains(chunkWrapper.getChunkPos()))
{
@@ -237,6 +246,11 @@ public class SharedApi
// lighting the chunk needs to be done on a separate thread to prevent lagging any of the event threads
ThreadPoolExecutor executor = ThreadPools.getLightPopulatorExecutor();
if (executor == null)
{
return;
}
executor.execute(() ->
{
LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
@@ -245,7 +259,8 @@ 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
if (chunkWrapper.isLightCorrect())
boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get();
if (!onlyUseDhLighting && chunkWrapper.isLightCorrect())
{
try
{
@@ -547,17 +547,29 @@ public class Config
// + "Disable this if you see LODs disappearing at the corners of your vision.")
// .build();
public static ConfigEntry<EOverdrawPrevention> overdrawPrevention = new ConfigEntry.Builder<EOverdrawPrevention>()
/**
* @deprecated Use overdrawPrevention instead, will be removed when DH updates to MC 1.21 <br>
* After removal a float value will be used to control overdraw instead. <br>
*/
@Deprecated
public static ConfigEntry<EOverdrawPrevention> overdrawPreventionPreset = new ConfigEntry.Builder<EOverdrawPrevention>()
.set(EOverdrawPrevention.MEDIUM)
.comment("")
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
.setPerformance(EConfigEntryPerformance.NONE)
.build();
public static ConfigEntry<Double> overdrawPrevention = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0.0, 0.4, 1.0)
.comment(""
+ "Determines how far Distant Horizon's near clip plane will render. \n"
+ "Determines how far from the camera Distant Horizons will start rendering. \n"
+ "Measured as a percentage of the vanilla render distance.\n"
+ "\n"
+ "Higher values will prevent LODs from rendering behind vanilla blocks at a higher distance,\n"
+ "but may cause holes to appear in the LODs. \n"
+ "Holes are most likely at the left and right edges of the screen \n"
+ "when flying through unloaded terrain. \n"
+ "Holes are most likely to appear when flying through unloaded terrain. \n"
+ "\n"
+ "Increasing the vanilla render distance increases the effectiveness of these options."
+ "Increasing the vanilla render distance increases the effectiveness of this setting."
+ "")
.setPerformance(EConfigEntryPerformance.NONE)
.build();
@@ -769,6 +781,19 @@ public class Config
+ "")
.build();
public static ConfigEntry<Boolean> onlyUseDhLightingEngine = new ConfigEntry.Builder<Boolean>()
.set(false)
.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"
+ "\n"
+ "If true LODs will only be lit using Distant Horizons' lighting engine. \n"
+ "\n"
+ "Generally it is best to leave this disabled and should only be enabled \n"
+ "if there are lighting issues or for debugging. \n"
+ "")
.build();
}
public static class Multiplayer
@@ -1447,14 +1472,26 @@ public class Config
try
{
// listener can only be added after the config has finished initializing
Client.Advanced.Graphics.AdvancedGraphics.overdrawPreventionPreset.addListener(OverdrawPreventionPresetConfigEventHandler.INSTANCE);
}
catch (Exception e)
{
LOGGER.error("Unexpected exception when running config delayed listener setup. Error: [" + e.getMessage() + "].", e);
}
try
{
// TODO automatically get all instances of AbstractPresetConfigEventHandler and fire "setUiOnlyConfigValues"
ThreadPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
OverdrawPreventionPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
RenderCacheConfigEventHandler.getInstance();
}
catch (Exception e)
{
LOGGER.error("Unexpected exception when setting up complicated config listeners. Error: [" + e.getMessage() + "].", e);
LOGGER.error("Unexpected exception when running config delayed UI setup. Error: [" + e.getMessage() + "].", e);
}
}
}
@@ -0,0 +1,86 @@
/*
* 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.config.eventHandlers.presets;
import com.seibel.distanthorizons.api.enums.config.EMaxHorizontalResolution;
import com.seibel.distanthorizons.api.enums.config.EOverdrawPrevention;
import com.seibel.distanthorizons.api.enums.config.quickOptions.EQualityPreset;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.ConfigEntryWithPresetOptions;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConfigEntry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
public class OverdrawPreventionPresetConfigEventHandler extends AbstractPresetConfigEventHandler<EOverdrawPrevention>
{
public static final OverdrawPreventionPresetConfigEventHandler INSTANCE = new OverdrawPreventionPresetConfigEventHandler();
private static final Logger LOGGER = LogManager.getLogger();
private final ConfigEntryWithPresetOptions<EOverdrawPrevention, Double> overdrawPrevention = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention,
new HashMap<EOverdrawPrevention, Double>()
{{
this.put(EOverdrawPrevention.HEAVY, 0.6);
this.put(EOverdrawPrevention.MEDIUM, 0.4);
this.put(EOverdrawPrevention.LIGHT, 0.25);
this.put(EOverdrawPrevention.NONE, 0.0);
}});
//==============//
// constructors //
//==============//
/** private since we only ever need one handler at a time */
private OverdrawPreventionPresetConfigEventHandler()
{
// add each config used by this preset
this.configList.add(this.overdrawPrevention);
for (ConfigEntryWithPresetOptions<EOverdrawPrevention, ?> config : this.configList)
{
// ignore try-using, the listener should only ever be added once and should never be removed
new ConfigChangeListener<>(config.configEntry, (val) -> { this.onConfigValueChanged(); });
}
}
//==============//
// enum getters //
//==============//
@Override
protected IConfigEntry<EOverdrawPrevention> getPresetConfigEntry() { return Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPreventionPreset; }
@Override
protected List<EOverdrawPrevention> getPresetEnumList() { return Arrays.asList(EOverdrawPrevention.values()); }
@Override
protected EOverdrawPrevention getCustomPresetEnum() { return EOverdrawPrevention.CUSTOM; }
}
@@ -25,7 +25,6 @@ import com.seibel.distanthorizons.core.config.ConfigBase;
import com.seibel.distanthorizons.core.config.types.AbstractConfigType;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -112,7 +111,7 @@ public class ConfigFileHandling
// Attempt to get the version number
currentCfgVersion = (Integer) tmpNightConfig.get("_version");
tmpNightConfig.close();
} catch (Exception e) {e.printStackTrace();}
} catch (Exception ignored) { }
if (currentCfgVersion == configBase.configVersion)
{}
@@ -227,9 +226,21 @@ public class ConfigFileHandling
return;
}
entry.pureSet((T) ConfigTypeConverters.attemptToConvertFromString(entry.getType(), nightConfig.get(entry.getNameWCategory())));
// try converting the value if necessary
Class<?> expectedValueClass = entry.getType();
Object value = nightConfig.get(entry.getNameWCategory());
Object convertedValue = ConfigTypeConverters.attemptToConvertFromString(expectedValueClass, value);
if (!convertedValue.getClass().equals(expectedValueClass))
{
LOGGER.error("Unable to convert config value ["+value+"] from ["+(value != null ? value.getClass() : "NULL")+"] to ["+expectedValueClass+"] for config ["+entry.name+"], " +
"the default config value will be used instead ["+entry.getDefaultValue()+"]. " +
"Make sure a converter is defined in ["+ConfigTypeConverters.class.getSimpleName()+"].");
convertedValue = entry.getDefaultValue();
}
entry.pureSet((T) convertedValue);
if (entry.getTrueValue() == null) {
if (entry.getTrueValue() == null)
{
LOGGER.warn("Entry [" + entry.getNameWCategory() + "] returned as null from the config. Using default value.");
entry.pureSet(entry.getDefaultValue());
}
@@ -37,12 +37,13 @@ public class ConfigTypeConverters
// Once you've made a converter add it to here where the first value is the type you want to convert and the 2nd value is the converter
public static final Map<Class<?>, ConverterBase> convertObjects = new HashMap<Class<?>, ConverterBase>()
{{
put(Short.class, new ShortConverter());
put(Long.class, new LongConverter());
put(Float.class, new FloatConverter());
put(Byte.class, new ByteConverter());
this.put(Short.class, new ShortConverter());
this.put(Long.class, new LongConverter());
this.put(Float.class, new FloatConverter());
this.put(Double.class, new DoubleConverter());
this.put(Byte.class, new ByteConverter());
put(Map.class, new MapConverter());
this.put(Map.class, new MapConverter());
}};
public static Class<?> isClassConvertable(Class<?> clazz)
@@ -73,10 +74,12 @@ public class ConfigTypeConverters
{
return attemptToConvertFromString(value.getClass(), value);
}
public static Object attemptToConvertFromString(Class<?> clazz, Object value)
public static Object attemptToConvertFromString(Class<?> outputClass, Object value)
{
Class<?> convertablClass = isClassConvertable(clazz);
if (convertablClass != null) {
boolean valueNeedsConverting = (value == null || value.getClass().equals(String.class));
Class<?> convertablClass = isClassConvertable(outputClass);
if (valueNeedsConverting && convertablClass != null)
{
return convertFromString(convertablClass, (String) value);
}
return value;
@@ -140,7 +143,12 @@ public class ConfigTypeConverters
{
@Override public String convertToString(Object item) { return ((Float) item).toString(); }
@Override public Float convertFromString(String s) { return Float.valueOf(s); }
}
public static class DoubleConverter extends ConverterBase
{
@Override public String convertToString(Object item) { return ((Double) item).toString(); }
@Override public Double convertFromString(String s) { return Double.valueOf(s); }
}
public static class ByteConverter extends ConverterBase
@@ -32,7 +32,9 @@ public enum EConfigEntryAppearance
/** Will only show the option in the UI. The option will be reverted on game restart */
ONLY_IN_GUI(true, false),
/** Only show the option in the file. There would be no way to access it using the UI */
ONLY_IN_FILE(true, false);
ONLY_IN_FILE(true, false),
/** The option is only available via code. Generally this is only used for deprecated options. */
ONLY_IN_API(false, false);
/** Sets whether the option should show in the UI */
public final boolean showInGui;
@@ -52,6 +52,8 @@ public class ColumnBox
// 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
// FIXME this transparency change should be applied before this point since this could affect other areas
// This may also be better than handling the LOD as transparent, but that is TBD
if (!RenderDataPointUtil.doesDataPointExist(bottomData))
{
color = ColorUtil.setAlpha(color, 255);
@@ -269,7 +271,17 @@ public class ColumnBox
adjIndex++)
{
long adjPoint = adjColumnView.get(adjIndex);
boolean adjTransparent = RenderDataPointUtil.getAlpha(adjPoint) < 255 && LodRenderer.transparencyEnabled;
// if the adjacent data point is over the void
// don't consider it as transparent
// FIXME this transparency change should be applied before this point since this could affect other areas
boolean adjOverVoid = false;
if (adjIndex > 0)
{
long adjBellowPoint = adjColumnView.get(adjIndex-1);
adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBellowPoint);
}
boolean adjTransparent = !adjOverVoid && RenderDataPointUtil.getAlpha(adjPoint) < 255 && LodRenderer.transparencyEnabled;
// continue if this data point is transparent or the adjacent point is not
@@ -39,6 +39,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Used to populate the buffers in a {@link ColumnRenderSource} object.
@@ -66,6 +67,17 @@ public class ColumnRenderBufferBuilder
IDhClientLevel clientLevel, Reference<ColumnRenderBuffer> renderBufferRef,
ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
{
ThreadPoolExecutor bufferBuilderExecutor = ThreadPools.getBufferBuilderExecutor();
ThreadPoolExecutor bufferUploaderExecutor = ThreadPools.getBufferUploaderExecutor();
if ((bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated()) ||
(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;
}
//LOGGER.info("RenderRegion startBuild @ "+renderSource.sectionPos);
return CompletableFuture.supplyAsync(() ->
{
@@ -101,7 +113,7 @@ public class ColumnRenderBufferBuilder
LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3);
throw e3;
}
}, ThreadPools.getBufferBuilderExecutor())
}, bufferBuilderExecutor)
.thenApplyAsync((quadBuilder) ->
{
try
@@ -136,7 +148,7 @@ public class ColumnRenderBufferBuilder
LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3);
throw e3;
}
}, ThreadPools.getBufferUploaderExecutor())
}, bufferUploaderExecutor)
.handle((columnRenderBuffer, ex) ->
{
//LOGGER.info("RenderRegion endBuild @ {}", renderSource.sectionPos);
@@ -168,10 +180,11 @@ public class ColumnRenderBufferBuilder
// Variable initialization
EDebugRendering debugMode = Config.Client.Advanced.Debugging.debugRendering.get();
// TODO make a config for this
// can be uncommented to limit which section positions are build and thus, rendered
// useful when debugging a specific section
// if (renderSource.sectionPos.sectionDetailLevel == 6
// && renderSource.sectionPos.sectionZ == 0 && renderSource.sectionPos.sectionX == 0)
// if (renderSource.sectionPos.getDetailLevel() == 6
// && renderSource.sectionPos.getZ() == 0 && renderSource.sectionPos.getX() == 0)
// {
// int test = 0;
// }
@@ -185,10 +198,11 @@ public class ColumnRenderBufferBuilder
{
for (int z = 0; z < ColumnRenderSource.SECTION_SIZE; z++)
{
// TODO make a config for this
// can be uncommented to limit the buffer building to a specific
// relative position in this section.
// useful for debugging a single column's rendering
// if (x != 1 || z != 1)
// if (x != 0 || (z != 0 && z != 1))
// {
// continue;
// }
@@ -304,6 +318,7 @@ public class ColumnRenderBufferBuilder
// We only stop when we find a block that is void or non-existing block
for (int i = 0; i < columnRenderData.size(); i++)
{
// TODO make a config for this
// can be uncommented to limit which vertical LOD is generated
// if (i != 0)
// {
@@ -25,6 +25,7 @@ public interface IColumnDataView
{
long get(int index);
// FIXME probably horizontal size in blocks?
int size();
default Iterator<Long> iterator()
@@ -43,8 +44,10 @@ public interface IColumnDataView
};
}
// FIXME measured in blocks?
int verticalSize();
// FIXME how many datapoints in this LOD?
int dataCount();
IColumnDataView subView(int dataIndexStart, int dataCount);
@@ -100,6 +100,12 @@ public class ChunkToLodBuilder implements AutoCloseable
return;
}
ThreadPoolExecutor lodBuilderExecutor = ThreadPools.getChunkToLodBuilderExecutor();
if (lodBuilderExecutor == null)
{
return;
}
for (int i = 0; i < threadCount; i++)
{
@@ -114,7 +120,7 @@ public class ChunkToLodBuilder implements AutoCloseable
{
this.runningCount.decrementAndGet();
}
}, ThreadPools.getChunkToLodBuilderExecutor());
}, lodBuilderExecutor);
}
}
private void tickThreadTask()
@@ -280,7 +280,7 @@ public class FullDataToRenderDataTransformer
// solid block check
if (avoidSolidBlocks && !block.isSolid() && !block.isLiquid())
if (avoidSolidBlocks && !block.isSolid() && !block.isLiquid() && block.getOpacity() != IBlockStateWrapper.FULLY_OPAQUE)
{
if (colorBelowWithAvoidedBlocks)
{
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.dataObjects.transformers;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
@@ -28,12 +29,15 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
public class LodDataBuilder
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IBlockStateWrapper AIR = SingletonInjector.INSTANCE.get(IWrapperFactory.class).getAirBlockStateWrapper();
private static boolean getTopErrorLogged = false;
public static ChunkSizedFullDataAccessor createChunkData(IChunkWrapper chunkWrapper)
{
@@ -65,10 +69,24 @@ public class LodDataBuilder
IBlockStateWrapper topBlockState = chunkWrapper.getBlockState(x, y, z);
while (!topBlockState.isAir() && y < chunkWrapper.getMaxBuildHeight())
{
// This is necessary in some edge cases with snow layers and some other blocks that may not appear in the height map but do block light.
// Interestingly this doesn't appear to be the case in the DhLightingEngine, if this same logic is added there the lighting breaks for the affected blocks.
y++;
topBlockState = chunkWrapper.getBlockState(x, y, z);
try
{
// This is necessary in some edge cases with snow layers and some other blocks that may not appear in the height map but do block light.
// Interestingly this doesn't appear to be the case in the DhLightingEngine, if this same logic is added there the lighting breaks for the affected blocks.
y++;
topBlockState = chunkWrapper.getBlockState(x, y, z);
}
catch (Exception e)
{
if (!getTopErrorLogged)
{
LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + chunkWrapper.getMaxBuildHeight() + "] had issue getting block at pos [" + x + "," + y + "," + z + "] error: " + e.getMessage(), e);
getTopErrorLogged = true;
}
y--;
break;
}
}
@@ -247,7 +247,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
ThreadPoolExecutor executor = ThreadPools.getFileHandlerExecutor();
if (!executor.isTerminated())
if (executor != null && !executor.isTerminated())
{
// load the data source
@@ -344,7 +344,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
else
{
ThreadPoolExecutor executor = ThreadPools.getFileHandlerExecutor();
if (!executor.isTerminated())
if (executor != null && !executor.isTerminated())
{
// wait for the update to finish before returning the data source
@@ -104,7 +104,7 @@ public class RenderSourceFileHandler implements IRenderSourceProvider
{
// don't continue if the handler has been shut down
ThreadPoolExecutor executor = ThreadPools.getFileHandlerExecutor();
if (executor.isTerminated())
if (executor != null && executor.isTerminated())
{
return CompletableFuture.completedFuture(null);
}
@@ -266,11 +266,14 @@ public class RenderSourceFileHandler implements IRenderSourceProvider
private String[] f3Log()
{
ThreadPoolExecutor executor = ThreadPools.getFileHandlerExecutor();
String queueSize = executor != null ? executor.getQueue().size()+"" : "-";
String completedTaskSize = executor != null ? executor.getCompletedTaskCount()+"" : "-";
ArrayList<String> lines = new ArrayList<>();
lines.add("Render Source File Handler [" + this.clientLevel.getClientLevelWrapper().getDimensionType().getDimensionName() + "]");
lines.add(" Loaded files: " + this.loadedMetaFileBySectionPos.size());
lines.add(" Thread pool tasks: " + executor.getQueue().size() + " (completed: " + executor.getCompletedTaskCount() + ")");
lines.add(" Thread pool tasks: " + queueSize + " (completed: " + completedTaskSize + ")");
int totalFutures = this.taskTracker.size();
EnumMap<ETaskType, Integer> tasksOutstanding = new EnumMap<>(ETaskType.class);
@@ -57,7 +57,7 @@ public class SubDimCompare implements Comparable<SubDimCompare>
}
/** returns a number between 0 (no equal datapoint) and 1 (totally equal) */
public double getPercentEqual() { return (double) equalDataPoints / (double) totalDataPoints; }
public double getPercentEqual() { return (double) this.equalDataPoints / (double) this.totalDataPoints; }
@Override
@@ -66,7 +66,7 @@ public class SubDimCompare implements Comparable<SubDimCompare>
if (this.equalDataPoints != other.equalDataPoints)
{
// compare based on data points
return Integer.compare(this.equalDataPoints, other.equalDataPoints);
return Double.compare(this.getPercentEqual(), other.getPercentEqual());
}
else
{
@@ -83,4 +83,7 @@ public class SubDimCompare implements Comparable<SubDimCompare>
|| this.playerPosDist <= MAX_SIMILAR_PLAYER_POS_DISTANCE_IN_BLOCKS;
}
@Override
public String toString() { return this.equalDataPoints + "/" + this.totalDataPoints + ": " + this.getPercentEqual() + " playerPos: " + this.playerPosDist; }
}
@@ -332,7 +332,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
String subDimShortName = LodUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging
String equalPercent = LodUtil.shortenString(subDimCompare.getPercentEqual()+"", 5);
String equalPercent = LodUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5);
LOGGER.info("Sub dimension ["+subDimShortName+"...] is current dimension probability: "+equalPercent+" ("+equalDataPoints+"/"+totalDataPointCount+")");
}
catch (Exception e)
@@ -72,6 +72,8 @@ public class DhLightingEngine
DhChunkPos centerChunkPos = centerChunk.getChunkPos();
AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk);
long startTimeNs = System.nanoTime();
// try-finally to handle the stableArray resources
StableLightPosStack blockLightPosQueue = null;
@@ -143,25 +145,25 @@ public class DhLightingEngine
{
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
// get the light
int maxY = chunk.getLightBlockingHeightMapValue(relX, relZ);
DhBlockPos skyLightPos = new DhBlockPos(chunk.getMinBlockX() + relX, maxY, chunk.getMinBlockZ() + relZ);
if (skyLightPos.y < chunk.getMinBuildHeight() || skyLightPos.y > chunk.getMaxBuildHeight())
// set each pos' sky light all the way down until a opaque block is hit
for (int y = chunk.getMaxBuildHeight(); y >= chunk.getMinBuildHeight(); y--)
{
// this shouldn't normally happen
if (!warningLogged)
IBlockStateWrapper block = chunk.getBlockState(relX, y, relZ);
if (block != null && block.getOpacity() != IBlockStateWrapper.FULLY_TRANSPARENT)
{
warningLogged = true;
LOGGER.debug("Lighting chunk at pos " + chunk.getChunkPos() + " may have a missing or incomplete heightmap. Chunk min/max [" + chunk.getMinBuildHeight() + "/" + chunk.getMaxBuildHeight() + "], skylight pos: " + skyLightPos);
// keep moving down until we find a non-transparent block
break;
}
continue;
// add sky light to the queue
DhBlockPos skyLightPos = new DhBlockPos(chunk.getMinBlockX() + relX, y, chunk.getMinBlockZ() + relZ);
skyLightPosQueue.push(skyLightPos.x, skyLightPos.y, skyLightPos.z, maxSkyLight);
// set the chunk's sky light
skyLightPos.mutateToChunkRelativePos(relBlockPos);
chunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, maxSkyLight);
}
skyLightPosQueue.push(skyLightPos.x, skyLightPos.y, skyLightPos.z, maxSkyLight);
// set the light
skyLightPos.mutateToChunkRelativePos(relBlockPos);
chunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, maxSkyLight);
}
}
}
@@ -199,7 +201,10 @@ public class DhLightingEngine
centerChunk.setIsDhLightCorrect(true);
centerChunk.setUseDhLighting(true);
LOGGER.trace("Finished generating lighting for chunk: [" + centerChunkPos + "]");
long endTimeNs = System.nanoTime();
float totalTimeMs = (endTimeNs - startTimeNs) / 1_000_000.0f;
LOGGER.trace("Finished generating lighting for chunk: [" + centerChunkPos + "] in ["+totalTimeMs+"] milliseconds");
}
/** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */
@@ -518,7 +518,8 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
try
{
int waitTimeInSeconds = 3;
if (!ThreadPools.getWorldGenExecutor().awaitTermination(waitTimeInSeconds, TimeUnit.SECONDS))
ThreadPoolExecutor executor = ThreadPools.getWorldGenExecutor();
if (executor != null && !executor.awaitTermination(waitTimeInSeconds, TimeUnit.SECONDS))
{
LOGGER.warn("World generator thread pool shutdown didn't complete after [" + waitTimeInSeconds + "] seconds. Some world generator requests may still be running.");
}
@@ -74,13 +74,19 @@ public class WebDownloader
fos, 1024))
{
byte[] data = new byte[1024];
int i;
int i, percent = -1;
while ((i = in.read(data, 0, 1024)) >= 0)
{
totalDataRead = totalDataRead + i;
bout.write(data, 0, i);
// int percent = (int) ((totalDataRead * 100) / filesize);
// System.out.println(percent);
// TODO: Link this to an atomic integer rather than printing it to log
int newPercent = (int) ((totalDataRead * 100) / filesize);
if (percent != newPercent)
{
percent = newPercent;
System.out.println(String.valueOf(percent) +"% downloaded");
}
}
}
}
@@ -117,7 +117,7 @@ public class SelfUpdater
LOGGER.info("New version (" + ModrinthGetter.getLatestNameForVersion(mcVersion) + ") of " + ModInfo.READABLE_NAME + " is available");
newFileLocation = JarUtils.jarFile.getParentFile().toPath().resolve("update").resolve(ModInfo.NAME + "-" + ModrinthGetter.getLatestNameForVersion(mcVersion) + ".jar").toFile();
newFileLocation = JarUtils.jarFile.getParentFile().toPath().resolve("update").resolve(ModInfo.NAME + "-" + ModrinthGetter.getLatestNameForVersion(mcVersion) + "-" + mcVersion + ".jar").toFile();
if (Config.Client.Advanced.AutoUpdater.enableSilentUpdates.get())
{
// Auto-update mod
@@ -141,7 +141,7 @@ public class SelfUpdater
if (!pipeline.get("status").equals("success"))
{
LOGGER.warn("Pipeline for branch ["+ ModJarInfo.Git_Branch +"], commit ["+ pipeline.get("id") +"], has either failed to build, or still building.");
LOGGER.warn("Pipeline for branch ["+ ModJarInfo.Git_Branch +"], pipeline ID ["+ pipeline.get("id") +"], has either failed to build, or is still building.");
return false;
}
@@ -138,8 +138,15 @@ public class DhClientLevel extends DhLevel implements IDhClientLevel
@Override
public void clientTick()
{
this.chunkToLodBuilder.tick();
this.clientside.clientTick();
try
{
this.chunkToLodBuilder.tick();
this.clientside.clientTick();
}
catch (Exception e)
{
LOGGER.error("Unexpected clientTick Exception: "+e.getMessage(), e);
}
}
public void doWorldGen()
@@ -324,10 +324,10 @@ public class LodRenderSection implements IDebugRenderable
if (showRenderSectionStatus && this.pos.getDetailLevel() == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
{
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(this.pos, 32f, 64f, 0.2f, Color.yellow),
0.5, 16f
)
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(this.pos, 32f, 64f, 0.2f, Color.yellow),
0.5, 16f
)
);
}
@@ -30,6 +30,7 @@ import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexAtt
import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexPointer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.render.fog.LodFogConfig;
import com.seibel.distanthorizons.core.util.RenderUtil;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.coreapi.util.math.Vec3f;
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
@@ -54,7 +55,8 @@ public class LodRenderProgram extends ShaderProgram
public final int lightMapUniform;
// Fog Uniforms
// Fog/Clip Uniforms
public final int clipDistanceUniform;
// Noise Uniforms
public final int noiseEnabledUniform;
@@ -84,6 +86,8 @@ public class LodRenderProgram extends ShaderProgram
lightMapUniform = getUniformLocation("lightMap");
// Fog/Clip Uniforms
clipDistanceUniform = getUniformLocation("clipDistance");
// Noise Uniforms
noiseEnabledUniform = getUniformLocation("noiseEnabled");
@@ -166,10 +170,10 @@ public class LodRenderProgram extends ShaderProgram
vao.unbindBuffersFromAllBindingPoint();
}
public void fillUniformData(Mat4f combinedMatrix, int lightmapBindPoint, int worldYOffset, int vanillaDrawDistance)
public void fillUniformData(Mat4f combinedMatrix, int lightmapBindPoint, int worldYOffset, float partialTicks)
{
super.bind();
vanillaDrawDistance += 32; // Give it a 2 chunk boundary for near fog.
// uniforms
setUniform(combinedMatUniform, combinedMatrix);
setUniform(mircoOffsetUniform, 0.01f); // 0.01 block offset
@@ -182,6 +186,9 @@ public class LodRenderProgram extends ShaderProgram
// Debug
setUniform(whiteWorldUniform, Config.Client.Advanced.Debugging.enableWhiteWorld.get());
// Fog/Clip Uniforms
float dhNearClipDistance = RenderUtil.getNearClipPlaneDistanceInBlocks(partialTicks);
setUniform(clipDistanceUniform, dhNearClipDistance);
}
public void setModelPos(Vec3f modelPos)
@@ -254,7 +254,7 @@ public class LodRenderer
// and often do change the projection entirely, as well as the output usage.
//EVENT_LOGGER.debug("Skipping shadow pass render.");
//return;
return;
}
// Note: Since lightmapTexture is changing every frame, it's faster to recreate it than to reuse the old one.
@@ -365,7 +365,7 @@ public class LodRenderer
}
/*---------Get required data--------*/
int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
//int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
//Mat4f modelViewProjectionMatrix = RenderUtil.createCombinedModelViewProjectionMatrix(baseProjectionMatrix, baseModelViewMatrix, partialTicks);
Mat4f projectionMatrix = RenderUtil.createLodProjectionMatrix(baseProjectionMatrix, partialTicks);
@@ -375,7 +375,7 @@ public class LodRenderer
/*---------Fill uniform data--------*/
this.shaderProgram.fillUniformData(modelViewProjectionMatrix, /*Light map = GL_TEXTURE0*/ 0,
MC.getWrappedClientLevel().getMinHeight(), vanillaBlockRenderedDistance);
MC.getWrappedClientLevel().getMinHeight(), partialTicks);
lightmap.bind();
if (ENABLE_IBO)
@@ -256,12 +256,17 @@ public class RenderDataPointUtil
// TODO this should probably be moved
// TODO what is the purpose of these?
//these were needed by the old logic for mergeMultiData(),
//which has now been replaced by RenderDataPointReducingList.
//so, these are no longer necessary, but left here for the same
//reason the old logic is left here: in case it's ever needed again.
/*
private static final ThreadLocal<int[]> tLocalIndices = new ThreadLocal<>();
private static final ThreadLocal<boolean[]> tLocalIncreaseIndex = new ThreadLocal<>();
private static final ThreadLocal<boolean[]> tLocalIndexHandled = new ThreadLocal<>();
private static final ThreadLocal<short[]> tLocalHeightAndDepth = new ThreadLocal<>();
private static final ThreadLocal<int[]> tDataIndexCache = new ThreadLocal<>();
*/
/**
* This method merge column of multiple data together
@@ -271,6 +276,32 @@ public class RenderDataPointUtil
*/
public static void mergeMultiData(IColumnDataView sourceData, ColumnArrayView output)
{
int target = output.verticalSize();
if (target <= 0)
{
// I expect this to never be the case,
// but RenderDataPointReducingList handles it sanely,
// so I might as well handle it sanely here too.
output.fill(EMPTY_DATA);
}
else if (target == 1)
{
output.set(0, RenderDataPointReducingList.reduceToOne(sourceData));
for (int index = 1, size = output.size(); index < size; index++)
{
output.set(index, EMPTY_DATA);
}
}
else
{
RenderDataPointReducingList list = new RenderDataPointReducingList(sourceData);
list.reduce(output.verticalSize());
list.copyTo(output);
}
//old logic left here in case it's ever needed again.
/*
if (output.dataCount() != 1)
{
throw new IllegalArgumentException("output must be only reserved for one datapoint!");
@@ -612,6 +643,7 @@ public class RenderDataPointUtil
}
}
*/
}
}
@@ -145,16 +145,21 @@ public class RenderUtil
*/
public static Mat4f createLodProjectionMatrix(Mat4f mcProjMat, float partialTicks)
{
// in James' testing a near clip plane distance of 2 blocks is enough to allow the fragment
// culling to take effect instead of seeing the near clip plane.
float nearClipDist = 2f; //MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH / 4.0f; //getNearClipPlaneDistanceInBlocks(partialTicks);
if (Config.Client.Advanced.Debugging.lodOnlyMode.get())
{
nearClipDist = 0.1f;
}
int farPlaneDistanceInBlocks = RenderUtil.getFarClipPlaneDistanceInBlocks();
float farClipDist = (float) ((farPlaneDistanceInBlocks + LodUtil.REGION_WIDTH) * Math.sqrt(2));
// Create a copy of the current matrix, so it won't be modified.
Mat4f lodProj = mcProjMat.copy();
// Set new far and near clip plane values.
lodProj.setClipPlanes(
getNearClipPlaneDistanceInBlocks(partialTicks),
(float) ((farPlaneDistanceInBlocks + LodUtil.REGION_WIDTH) * Math.sqrt(2)));
lodProj.setClipPlanes(nearClipDist, farClipDist);
return lodProj;
}
@@ -198,34 +203,21 @@ public class RenderUtil
else
{
// TODO make this option dependent on player speed.
// if the player is flying quickly, lower the near clip plane to account for slow chunk loading.
// If the player is flying quickly, lower the near clip plane to account for slow chunk loading.
// If the player is moving quickly they are less likely to notice overdraw.
EOverdrawPrevention clipPlaneDistance = Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention.get();
switch (clipPlaneDistance)
nearClipPlane = Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention.get().floatValue();
nearClipPlane *= vanillaBlockRenderedDistance;
// the near clip plane should never be closer than 1/10th of a block,
// otherwise Z-fighting and other issues may occur
if (nearClipPlane < 0.1f)
{
default: // shouldn't be necessary, just here to make the compiler happy
case NONE:
nearClipPlane = 0.1f;
break;
case LIGHT:
nearClipPlane = vanillaBlockRenderedDistance * 0.25f;
break;
case MEDIUM:
nearClipPlane = vanillaBlockRenderedDistance * 0.4f;
break;
case HEAVY:
// recommend render distance ot 6 or higher, otherwise holes may appear
nearClipPlane = vanillaBlockRenderedDistance * 0.6f;
break;
nearClipPlane = 0.1f;
}
}
// modify the based on the player's FOV
// modify based on the player's FOV
double fov = MC_RENDER.getFov(partialTicks);
double aspectRatio = (double) MC_RENDER.getScreenWidth() / MC_RENDER.getScreenHeight();
@@ -90,9 +90,8 @@ public class ConfigThreadPool
{
if (this.executor != null)
{
//LOGGER.info("Stopping File Handler");
//LOGGER.info("Stopping thread pool");
this.executor.shutdownNow();
this.executor = null;
}
this.threadCount = 0;
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.util.threading;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
@@ -42,13 +43,16 @@ public class ThreadPools
public static final DhThreadFactory FILE_HANDLER_THREAD_FACTORY = new DhThreadFactory("File Handler", Thread.MIN_PRIORITY);
private static ConfigThreadPool fileHandlerThreadPool;
@Nullable
public static ThreadPoolExecutor getFileHandlerExecutor() { return fileHandlerThreadPool.executor; }
public static final DhThreadFactory WORLD_GEN_THREAD_FACTORY = new DhThreadFactory("World Gen", Thread.MIN_PRIORITY);
private static ConfigThreadPool worldGenThreadPool;
@Nullable
public static ThreadPoolExecutor getWorldGenExecutor() { return worldGenThreadPool.executor; }
private static ThreadPoolExecutor bufferUploaderThreadPool;
@Nullable
public static ThreadPoolExecutor getBufferUploaderExecutor() { return bufferUploaderThreadPool; }
@@ -63,14 +67,17 @@ public class ThreadPools
public static final DhThreadFactory LIGHT_POPULATOR_THREAD_FACTORY = new DhThreadFactory("LOD Builder - Light Populator", Thread.MIN_PRIORITY);
private static ConfigThreadPool lightPopulatorThreadPool;
@Nullable
public static ThreadPoolExecutor getLightPopulatorExecutor() { return lightPopulatorThreadPool.executor; }
public static final DhThreadFactory CHUNK_TO_LOD_BUILDER_THREAD_FACTORY = new DhThreadFactory("LOD Builder - Chunk to Lod Builder", Thread.MIN_PRIORITY);
private static ConfigThreadPool chunkToLodBuilderThreadPool;
@Nullable
public static ThreadPoolExecutor getChunkToLodBuilderExecutor() { return chunkToLodBuilderThreadPool.executor; }
public static final DhThreadFactory BUFFER_BUILDER_THREAD_FACTORY = new DhThreadFactory("LOD Builder - Buffer Builder", Thread.MIN_PRIORITY);
private static ConfigThreadPool bufferBuilderThreadPool;
@Nullable
public static ThreadPoolExecutor getBufferBuilderExecutor() { return bufferBuilderThreadPool.executor; }
@@ -29,6 +29,7 @@ import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.HashMap;
@@ -69,7 +70,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
//=========//
@Override
public DhClientServerLevel getOrLoadLevel(ILevelWrapper wrapper)
public DhClientServerLevel getOrLoadLevel(@NotNull ILevelWrapper wrapper)
{
if (wrapper instanceof IServerLevelWrapper)
{
@@ -105,13 +106,13 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
}
@Override
public DhClientServerLevel getLevel(ILevelWrapper wrapper) { return this.levelWrapperByDhLevel.get(wrapper); }
public DhClientServerLevel getLevel(@NotNull ILevelWrapper wrapper) { return this.levelWrapperByDhLevel.get(wrapper); }
@Override
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.dhLevels; }
@Override
public void unloadLevel(ILevelWrapper wrapper)
public void unloadLevel(@NotNull ILevelWrapper wrapper)
{
if (this.levelWrapperByDhLevel.containsKey(wrapper))
{
@@ -160,7 +161,12 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor
this.saveAndFlush();
this.f3Message.close();
for (DhClientServerLevel level : this.dhLevels)
// clear dhLevels to prevent concurrent modification errors
HashSet<DhClientServerLevel> levelsToClose = new HashSet<>(this.dhLevels);
this.dhLevels.clear();
// close each level
for (DhClientServerLevel level : levelsToClose)
{
LOGGER.info("Unloading level " + level.getServerLevelWrapper().getDimensionType().getDimensionName());
@@ -31,6 +31,7 @@ import com.seibel.distanthorizons.core.util.objects.EventLoop;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.NotNull;
import javax.annotation.CheckForNull;
import java.io.File;
@@ -84,7 +85,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
//=========//
@Override
public DhClientLevel getOrLoadLevel(ILevelWrapper wrapper)
public DhClientLevel getOrLoadLevel(@NotNull ILevelWrapper wrapper)
{
if (!(wrapper instanceof IClientLevelWrapper))
{
@@ -105,7 +106,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
}
@Override
public DhClientLevel getLevel(ILevelWrapper wrapper)
public DhClientLevel getLevel(@NotNull ILevelWrapper wrapper)
{
if (!(wrapper instanceof IClientLevelWrapper))
{
@@ -119,7 +120,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.levels.values(); }
@Override
public void unloadLevel(ILevelWrapper wrapper)
public void unloadLevel(@NotNull ILevelWrapper wrapper)
{
if (!(wrapper instanceof IClientLevelWrapper))
{
@@ -29,6 +29,7 @@ import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.HashMap;
@@ -48,7 +49,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
public DhServerWorld()
{
super(EWorldEnvironment.Server_Only);
this.saveStructure = new LocalSaveStructure();
this.levels = new HashMap<>();
@@ -57,7 +58,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
LOGGER.info("Started "+DhServerWorld.class.getSimpleName()+" of type "+this.environment);
}
//=========//
@@ -79,15 +80,15 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
this.getLevel(dest).addPlayer(player);
this.getLevel(origin).removePlayer(player);
}
@Override
public DhServerLevel getOrLoadLevel(ILevelWrapper wrapper)
public DhServerLevel getOrLoadLevel(@NotNull ILevelWrapper wrapper)
{
if (!(wrapper instanceof IServerLevelWrapper))
{
return null;
}
return this.levels.computeIfAbsent((IServerLevelWrapper) wrapper, (serverLevelWrapper) ->
{
File levelFile = this.saveStructure.getLevelFolder(wrapper);
@@ -95,29 +96,29 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
return new DhServerLevel(this.saveStructure, serverLevelWrapper, this.remotePlayerConnectionHandler);
});
}
@Override
public DhServerLevel getLevel(ILevelWrapper wrapper)
public DhServerLevel getLevel(@NotNull ILevelWrapper wrapper)
{
if (!(wrapper instanceof IServerLevelWrapper))
{
return null;
}
return this.levels.get(wrapper);
}
@Override
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.levels.values(); }
@Override
public void unloadLevel(ILevelWrapper wrapper)
public void unloadLevel(@NotNull ILevelWrapper wrapper)
{
if (!(wrapper instanceof IServerLevelWrapper))
{
return;
}
if (this.levels.containsKey(wrapper))
{
LOGGER.info("Unloading level {} ", this.levels.get(wrapper));
@@ -133,18 +134,18 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
public void doWorldGen() {
this.levels.values().forEach(DhServerLevel::doWorldGen);
}
@Override
public CompletableFuture<Void> saveAndFlush()
{
return CompletableFuture.allOf(this.levels.values().stream().map(DhServerLevel::saveAsync).toArray(CompletableFuture[]::new));
}
@Override
public void close()
{
this.remotePlayerConnectionHandler.close();
for (DhServerLevel level : this.levels.values())
{
LOGGER.info("Unloading level " + level.getLevelWrapper().getDimensionType().getDimensionName());
@@ -158,7 +159,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld
level.close();
}
this.levels.clear();
LOGGER.info("Closed DhWorld of type " + this.environment);
}
@@ -21,17 +21,18 @@ package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
public interface IDhWorld
{
IDhLevel getOrLoadLevel(ILevelWrapper levelWrapper);
IDhLevel getLevel(ILevelWrapper wrapper);
IDhLevel getOrLoadLevel(@NotNull ILevelWrapper levelWrapper);
IDhLevel getLevel(@NotNull ILevelWrapper wrapper);
Iterable<? extends IDhLevel> getAllLoadedLevels();
void unloadLevel(ILevelWrapper levelWrapper);
void unloadLevel(@NotNull ILevelWrapper levelWrapper);
CompletableFuture<Void> saveAndFlush();
@@ -25,6 +25,11 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
/** A Minecraft version independent way of handling Blocks. */
public interface IBlockStateWrapper extends IDhApiBlockStateWrapper
{
int FULLY_TRANSPARENT = 0;
int FULLY_OPAQUE = 16;
String getSerialString();
/**
@@ -268,7 +268,7 @@
"distanthorizons.config.client.advanced.graphics.advancedGraphics.overdrawPrevention":
"Overdraw Prevention",
"distanthorizons.config.client.advanced.graphics.advancedGraphics.overdrawPrevention.@tooltip":
"Enabling this will prevent some overdraw issues,\nbut may cause nearby LODs to render incorrectly, especially when near fancy leaves or non-full blocks.\nLess noticeable with a longer vanilla render distance.",
"Determines how far from the camera Distant Horizons will start rendering. \nMeasured as a percentage of the vanilla render distance.\n\nHigher values will prevent LODs from rendering behind vanilla blocks at a higher distance,\nbut may cause holes to appear in the LODs. \nHoles are most likely to appear when flying through unloaded terrain. \n\nIncreasing the vanilla render distance increases the effectiveness of this setting.",
"distanthorizons.config.client.advanced.graphics.advancedGraphics.seamlessOverdraw":
"(Experimental) Seamless Overdraw",
"distanthorizons.config.client.advanced.graphics.advancedGraphics.seamlessOverdraw.@tooltip":
@@ -335,6 +335,10 @@
"Minimum Time Between Chunk Updates In Seconds",
"distanthorizons.config.client.advanced.lodBuilding.minTimeBetweenChunkUpdatesInSeconds.@tooltip":
"Determines how long must pass between LOD chunk updates before another. \nupdate can occur\n\nIncreasing this value will reduce CPU load but may may cause \nLODs to become outdated more frequently or for longer.",
"distanthorizons.config.client.advanced.lodBuilding.onlyUseDhLightingEngine":
"Only Use DH Lighting Engine",
"distanthorizons.config.client.advanced.lodBuilding.onlyUseDhLightingEngine.@tooltip":
"If false LODs will be lit by Minecraft's lighting engine when possible \nand fall back to the DH lighting engine only when necessary. \n\nIf true LODs will only be lit using Distant Horizons' lighting engine. \n\nGenerally it is best to leave this disabled and should only be enabled \nif there are lighting issues or for debugging.",
"distanthorizons.config.client.advanced.multiplayer":
@@ -8,6 +8,9 @@ in vec4 vPos;
out vec4 fragColor;
// Fog/Clip Uniforms
uniform float clipDistance = 0.0;
// Noise Uniforms
uniform bool noiseEnabled;
uniform int noiseSteps;
@@ -24,47 +27,53 @@ float rand(vec3 co) { return rand(co.xy + rand(co.z)); }
// EG. setting stepSize to 4 then this would be the result of this function
// In: 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, ..., 1.1, 1.2, 1.3
// Out: 0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, ..., 1.0, 1.0, 1.25
vec3 quantize(vec3 val, int stepSize) {
vec3 quantize(vec3 val, int stepSize)
{
return floor(val * stepSize) / stepSize;
}
void applyNoise(inout vec4 fragColor, const in float viewDist)
{
vec3 vertexNormal = normalize(cross(dFdy(vPos.xyz), dFdx(vPos.xyz)));
// This bit of code is required to fix the vertex position problem cus of floats in the verted world position varuable
vec3 fixedVPos = vPos.xyz + vertexNormal * 0.001;
float noiseAmplification = noiseIntensity * 0.01;
float lum = (fragColor.r + fragColor.g + fragColor.b) / 3.0;
noiseAmplification = (1.0 - pow(lum * 2.0 - 1.0, 2.0)) * noiseAmplification; // Lessen the effect on depending on how dark the object is, equasion for this is -(2x-1)^{2}+1
noiseAmplification *= fragColor.a; // The effect would lessen on transparent objects
// Random value for each position
float randomValue = rand(quantize(fixedVPos, noiseSteps))
* 2.0 * noiseAmplification - noiseAmplification;
// Modifies the color
// A value of 0 on the randomValue will result in the original color, while a value of 1 will result in a fully bright color
vec3 newCol = fragColor.rgb + (1.0 - fragColor.rgb) * randomValue;
newCol = clamp(newCol, 0.0, 1.0);
if (noiseDropoff != 0) {
float distF = min(viewDist / noiseDropoff, 1.0);
newCol = mix(newCol, fragColor.rgb, distF); // The further away it gets, the less noise gets applied
}
fragColor.rgb = newCol;
}
/**
* Fragment Shader
*
* author: James Seibel
* author: coolGi
* version: 7-2-2023
*/
void main()
{
fragColor = vertexColor;
// TODO: Move into its own function instead of in an if statement
if (noiseEnabled) {
vec3 vertexNormal = normalize(cross(dFdy(vPos.xyz), dFdx(vPos.xyz)));
// This bit of code is required to fix the vertex position problem cus of floats in the verted world position varuable
vec3 fixedVPos = vPos.xyz + vertexNormal * 0.001;
float noiseAmplification = noiseIntensity * 0.01;
float lum = (fragColor.r + fragColor.g + fragColor.b) / 3.0;
noiseAmplification = (1.0 - pow(lum * 2.0 - 1.0, 2.0)) * noiseAmplification; // Lessen the effect on depending on how dark the object is, equasion for this is -(2x-1)^{2}+1
noiseAmplification *= fragColor.a; // The effect would lessen on transparent objects
// Random value for each position
float randomValue = rand(quantize(fixedVPos, noiseSteps))
* 2.0 * noiseAmplification - noiseAmplification;
// Modifies the color
// A value of 0 on the randomValue will result in the original color, while a value of 1 will result in a fully bright color
vec3 newCol = fragColor.rgb + (1.0 - fragColor.rgb) * randomValue;
newCol = clamp(newCol, 0.0, 1.0);
if (noiseDropoff != 0) {
float distF = min(length(vertexWorldPos) / noiseDropoff, 1.0);
newCol = mix(newCol, fragColor.rgb, distF); // The further away it gets, the less noise gets applied
}
fragColor.rgb = newCol;
float viewDist = length(vertexWorldPos);
if (viewDist < clipDistance && clipDistance > 0.0)
{
discard;
}
if (noiseEnabled)
{
applyNoise(fragColor, viewDist);
}
}