Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020-2022 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.distanthorizons.api.enums.config;
|
||||
|
||||
/**
|
||||
* MINECRAFT <br>
|
||||
* OLD_LIGHTING <br>
|
||||
* NONE <br>
|
||||
*/
|
||||
public enum ELodShading
|
||||
{
|
||||
// Reminder:
|
||||
// when adding items up the API minor version
|
||||
// when removing items up the API major version
|
||||
|
||||
MINECRAFT,
|
||||
OLD_LIGHTING,
|
||||
NONE;
|
||||
|
||||
}
|
||||
+3
-1
@@ -43,6 +43,8 @@ public enum EThreadPreset
|
||||
LOW_IMPACT,
|
||||
BALANCED,
|
||||
AGGRESSIVE,
|
||||
I_PAID_FOR_THE_WHOLE_CPU;
|
||||
|
||||
// temporarily removed due to stability concerns
|
||||
//I_PAID_FOR_THE_WHOLE_CPU;
|
||||
|
||||
}
|
||||
@@ -586,6 +586,20 @@ public class Config
|
||||
+ "If set to 0 the mod wont overwrite vanilla's default (which so happens to also be 0)")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<ELodShading> lodShading = new ConfigEntry.Builder<ELodShading>()
|
||||
.set(ELodShading.MINECRAFT)
|
||||
.comment(""
|
||||
+ "How should LODs be shaded? \n"
|
||||
+ "\n"
|
||||
+ ELodShading.MINECRAFT + ": Uses the same side shading as vanilla Minecraft blocks. \n"
|
||||
+ ELodShading.OLD_LIGHTING + ": Simulates Minecraft's block shading for LODs. \n"
|
||||
+ " Can be used to force LOD shading when using some shaders. \n"
|
||||
+ ELodShading.NONE + ": All LOD sides will be rendered with the same brightness. \n"
|
||||
+ "")
|
||||
.setPerformance(EConfigEntryPerformance.NONE)
|
||||
.addListener(RenderCacheConfigEventHandler.INSTANCE)
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -844,7 +858,7 @@ public class Config
|
||||
|
||||
public static final ConfigEntry<Integer> numberOfDataTransformerThreads = new ConfigEntry.Builder<Integer>()
|
||||
.setMinDefaultMax(1,
|
||||
ThreadPresetConfigEventHandler.getDataConverterDefaultThreadCount(),
|
||||
ThreadPresetConfigEventHandler.getDataTransformerDefaultThreadCount(),
|
||||
Runtime.getRuntime().availableProcessors())
|
||||
.comment(""
|
||||
+ "How many threads should be used when converting full ID data to render data? \n"
|
||||
@@ -859,7 +873,7 @@ public class Config
|
||||
+ THREAD_NOTE)
|
||||
.build();
|
||||
public static final ConfigEntry<Double> runTimeRatioForDataTransformerThreads = new ConfigEntry.Builder<Double>()
|
||||
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getDataConverterDefaultRunTimeRatio(), 1.0)
|
||||
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getDataTransformerDefaultRunTimeRatio(), 1.0)
|
||||
.comment(THREAD_RUN_TIME_RATIO_NOTE)
|
||||
.build();
|
||||
|
||||
|
||||
+9
@@ -1,6 +1,7 @@
|
||||
package com.seibel.distanthorizons.core.config.eventHandlers;
|
||||
|
||||
import com.seibel.distanthorizons.api.DhApi;
|
||||
import com.seibel.distanthorizons.api.enums.config.ELodShading;
|
||||
import com.seibel.distanthorizons.api.enums.config.EMaxHorizontalResolution;
|
||||
import com.seibel.distanthorizons.api.enums.config.EVerticalQuality;
|
||||
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||
@@ -22,6 +23,7 @@ public class RenderCacheConfigEventHandler implements IConfigListener
|
||||
// previous values used to check if a watched setting was actually modified
|
||||
private EVerticalQuality previousVerticalQualitySetting = null;
|
||||
private EMaxHorizontalResolution previousHorizontalResolution = null;
|
||||
private ELodShading lodShading = null;
|
||||
|
||||
/** how long to wait in milliseconds before applying the config changes */
|
||||
private static final long TIMEOUT_IN_MS = 400L;
|
||||
@@ -54,6 +56,13 @@ public class RenderCacheConfigEventHandler implements IConfigListener
|
||||
refreshRenderData = true;
|
||||
}
|
||||
|
||||
ELodShading newLodShading = Config.Client.Advanced.Graphics.AdvancedGraphics.lodShading.get();
|
||||
if (this.lodShading != newLodShading)
|
||||
{
|
||||
this.lodShading = newLodShading;
|
||||
refreshRenderData = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (refreshRenderData)
|
||||
|
||||
+23
-7
@@ -12,9 +12,11 @@ import java.util.*;
|
||||
public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<?>> implements IConfigListener
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static final long MS_DELAY_BEFORE_APPLYING_PRESET = 1_000;
|
||||
|
||||
protected final ArrayList<ConfigEntryWithPresetOptions<TPresetEnum, ?>> configList = new ArrayList<>();
|
||||
/** this timer is used so each preset isn't applied while a user is clicking through the config options */
|
||||
protected Timer presetApplicationTimer;
|
||||
|
||||
protected boolean changingPreset = false;
|
||||
|
||||
@@ -38,28 +40,42 @@ public abstract class AbstractPresetConfigEventHandler<TPresetEnum extends Enum<
|
||||
@Override
|
||||
public void onConfigValueSet()
|
||||
{
|
||||
TPresetEnum qualityPreset = this.getPresetConfigEntry().get();
|
||||
TPresetEnum presetEnum = this.getPresetConfigEntry().get();
|
||||
|
||||
// if the quick value is custom, nothing needs to be changed
|
||||
if (qualityPreset == this.getCustomPresetEnum())
|
||||
if (presetEnum == this.getCustomPresetEnum())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
LOGGER.debug("changing preset to: " + qualityPreset);
|
||||
// stop the previous timer if one exists
|
||||
if (this.presetApplicationTimer != null)
|
||||
{
|
||||
this.presetApplicationTimer.cancel();
|
||||
}
|
||||
|
||||
// reset the timer
|
||||
TimerTask task = new TimerTask() { public void run() { AbstractPresetConfigEventHandler.this.applyPreset(presetEnum); } };
|
||||
this.presetApplicationTimer = new Timer("PresetApplicationTimer");
|
||||
this.presetApplicationTimer.schedule(task, MS_DELAY_BEFORE_APPLYING_PRESET);
|
||||
|
||||
}
|
||||
private void applyPreset(TPresetEnum presetEnum)
|
||||
{
|
||||
LOGGER.debug("changing preset to: " + presetEnum);
|
||||
this.changingPreset = true;
|
||||
|
||||
for (ConfigEntryWithPresetOptions<TPresetEnum, ?> configEntry : this.configList)
|
||||
{
|
||||
configEntry.updateConfigEntry(qualityPreset);
|
||||
configEntry.updateConfigEntry(presetEnum);
|
||||
}
|
||||
|
||||
this.changingPreset = false;
|
||||
LOGGER.debug("preset active: "+qualityPreset);
|
||||
|
||||
LOGGER.debug("preset active: "+presetEnum);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onUiModify() { /* do nothing, we only care about modified config values */ }
|
||||
|
||||
|
||||
+30
-29
@@ -18,28 +18,29 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
|
||||
public static final ThreadPresetConfigEventHandler INSTANCE = new ThreadPresetConfigEventHandler();
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final boolean LOW_THREAD_COUNT_CPU = (Runtime.getRuntime().availableProcessors() <= 4);
|
||||
|
||||
|
||||
|
||||
public static int getWorldGenDefaultThreadCount() { return getThreadCountByPercent(0.1); }
|
||||
public static int getWorldGenDefaultThreadCount() { return getThreadCountByPercent(0.2); }
|
||||
private final ConfigEntryWithPresetOptions<EThreadPreset, Integer> worldGenThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads,
|
||||
new HashMap<EThreadPreset, Integer>()
|
||||
{{
|
||||
this.put(EThreadPreset.MINIMAL_IMPACT, 1);
|
||||
this.put(EThreadPreset.LOW_IMPACT, getWorldGenDefaultThreadCount());
|
||||
this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.2));
|
||||
this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.4));
|
||||
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.4));
|
||||
this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.6));
|
||||
//this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
}});
|
||||
public static double getWorldGenDefaultRunTimeRatio() { return 0.25; }
|
||||
public static double getWorldGenDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.5 : 1; }
|
||||
private final ConfigEntryWithPresetOptions<EThreadPreset, Double> worldGenRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads,
|
||||
new HashMap<EThreadPreset, Double>()
|
||||
{{
|
||||
this.put(EThreadPreset.MINIMAL_IMPACT, 0.1);
|
||||
this.put(EThreadPreset.MINIMAL_IMPACT, LOW_THREAD_COUNT_CPU ? 0.1 : 0.25);
|
||||
this.put(EThreadPreset.LOW_IMPACT, getWorldGenDefaultRunTimeRatio());
|
||||
this.put(EThreadPreset.BALANCED, 0.5);
|
||||
this.put(EThreadPreset.AGGRESSIVE, 0.75);
|
||||
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||
this.put(EThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.5 : 1.0);
|
||||
this.put(EThreadPreset.AGGRESSIVE, LOW_THREAD_COUNT_CPU ? 0.75 : 1.0);
|
||||
//this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||
}});
|
||||
|
||||
|
||||
@@ -51,17 +52,17 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
|
||||
this.put(EThreadPreset.LOW_IMPACT, getBufferBuilderDefaultThreadCount());
|
||||
this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.2));
|
||||
this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.4));
|
||||
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
//this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
}});
|
||||
public static double getBufferBuilderDefaultRunTimeRatio() { return 0.5; }
|
||||
public static double getBufferBuilderDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.5 : 0.75; }
|
||||
private final ConfigEntryWithPresetOptions<EThreadPreset, Double> bufferBuilderRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForBufferBuilderThreads,
|
||||
new HashMap<EThreadPreset, Double>()
|
||||
{{
|
||||
this.put(EThreadPreset.MINIMAL_IMPACT, 0.25);
|
||||
this.put(EThreadPreset.LOW_IMPACT, getBufferBuilderDefaultRunTimeRatio());
|
||||
this.put(EThreadPreset.BALANCED, 0.75);
|
||||
this.put(EThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.75 : 1.0);
|
||||
this.put(EThreadPreset.AGGRESSIVE, 1.0);
|
||||
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||
//this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||
}});
|
||||
|
||||
|
||||
@@ -73,7 +74,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
|
||||
this.put(EThreadPreset.LOW_IMPACT, getFileHandlerDefaultThreadCount());
|
||||
this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.2));
|
||||
this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.2));
|
||||
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
//this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
}});
|
||||
public static double getFileHandlerDefaultRunTimeRatio() { return 0.5; }
|
||||
private final ConfigEntryWithPresetOptions<EThreadPreset, Double> fileHandlerRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForFileHandlerThreads,
|
||||
@@ -83,29 +84,29 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
|
||||
this.put(EThreadPreset.LOW_IMPACT, getFileHandlerDefaultRunTimeRatio());
|
||||
this.put(EThreadPreset.BALANCED, 0.75);
|
||||
this.put(EThreadPreset.AGGRESSIVE, 1.0);
|
||||
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||
//this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||
}});
|
||||
|
||||
|
||||
public static int getDataConverterDefaultThreadCount() { return getThreadCountByPercent(0.1); }
|
||||
public static int getDataTransformerDefaultThreadCount() { return getThreadCountByPercent(0.1); }
|
||||
private final ConfigEntryWithPresetOptions<EThreadPreset, Integer> dataTransformerThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfDataTransformerThreads,
|
||||
new HashMap<EThreadPreset, Integer>()
|
||||
{{
|
||||
this.put(EThreadPreset.MINIMAL_IMPACT, 1);
|
||||
this.put(EThreadPreset.LOW_IMPACT, getDataConverterDefaultThreadCount());
|
||||
this.put(EThreadPreset.LOW_IMPACT, getDataTransformerDefaultThreadCount());
|
||||
this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.2));
|
||||
this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.2));
|
||||
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
//this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
}});
|
||||
public static double getDataConverterDefaultRunTimeRatio() { return 0.25; }
|
||||
public static double getDataTransformerDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.5 : 1; }
|
||||
private final ConfigEntryWithPresetOptions<EThreadPreset, Double> dataTransformerRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForDataTransformerThreads,
|
||||
new HashMap<EThreadPreset, Double>()
|
||||
{{
|
||||
this.put(EThreadPreset.MINIMAL_IMPACT, 0.1);
|
||||
this.put(EThreadPreset.LOW_IMPACT, getDataConverterDefaultRunTimeRatio());
|
||||
this.put(EThreadPreset.BALANCED, 0.75);
|
||||
this.put(EThreadPreset.MINIMAL_IMPACT, 0.25);
|
||||
this.put(EThreadPreset.LOW_IMPACT, getDataTransformerDefaultRunTimeRatio());
|
||||
this.put(EThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.75 : 1);
|
||||
this.put(EThreadPreset.AGGRESSIVE, 1.0);
|
||||
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||
//this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||
}});
|
||||
|
||||
|
||||
@@ -117,17 +118,17 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan
|
||||
this.put(EThreadPreset.LOW_IMPACT, getChunkLodConverterDefaultThreadCount());
|
||||
this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.2));
|
||||
this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.4));
|
||||
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
//this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, getThreadCountByPercent(1.0));
|
||||
}});
|
||||
public static double getChunkLodConverterDefaultRunTimeRatio() { return 0.5; }
|
||||
public static double getChunkLodConverterDefaultRunTimeRatio() { return LOW_THREAD_COUNT_CPU ? 0.5 : 0.75; }
|
||||
private final ConfigEntryWithPresetOptions<EThreadPreset, Double> chunkLodConverterRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForChunkLodConverterThreads,
|
||||
new HashMap<EThreadPreset, Double>()
|
||||
{{
|
||||
this.put(EThreadPreset.MINIMAL_IMPACT, 0.25);
|
||||
this.put(EThreadPreset.LOW_IMPACT, getDataConverterDefaultRunTimeRatio());
|
||||
this.put(EThreadPreset.BALANCED, 0.75);
|
||||
this.put(EThreadPreset.MINIMAL_IMPACT, LOW_THREAD_COUNT_CPU ? 0.1 : 0.25);
|
||||
this.put(EThreadPreset.LOW_IMPACT, getDataTransformerDefaultRunTimeRatio());
|
||||
this.put(EThreadPreset.BALANCED, LOW_THREAD_COUNT_CPU ? 0.75 : 1);
|
||||
this.put(EThreadPreset.AGGRESSIVE, 1.0);
|
||||
this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||
//this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0);
|
||||
}});
|
||||
|
||||
|
||||
|
||||
+3
-1
@@ -53,7 +53,9 @@ public class ColumnRenderBufferBuilder
|
||||
// vbo building //
|
||||
//==============//
|
||||
|
||||
public static CompletableFuture<ColumnRenderBuffer> buildBuffers(IDhClientLevel clientLevel, Reference<ColumnRenderBuffer> renderBufferRef, ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
|
||||
public static CompletableFuture<ColumnRenderBuffer> buildBuffersAsync(
|
||||
IDhClientLevel clientLevel, Reference<ColumnRenderBuffer> renderBufferRef,
|
||||
ColumnRenderSource renderSource, ColumnRenderSource[] adjData)
|
||||
{
|
||||
/* if (isBusy())
|
||||
{
|
||||
|
||||
+11
-6
@@ -278,11 +278,11 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
{
|
||||
return metaFile; // someone else loaded it already.
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
metaFile = RenderMetaDataFile.createFromExistingFile(this, fileToLoad);
|
||||
this.topDetailLevel.updateAndGet(v -> Math.max(v, pos.sectionDetailLevel));
|
||||
this.topDetailLevel.updateAndGet(newDetailLevel -> Math.max(newDetailLevel, pos.sectionDetailLevel));
|
||||
this.filesBySectionPos.put(pos, metaFile);
|
||||
return metaFile;
|
||||
}
|
||||
@@ -317,7 +317,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
return null;
|
||||
}
|
||||
|
||||
this.topDetailLevel.updateAndGet(v -> Math.max(v, pos.sectionDetailLevel));
|
||||
this.topDetailLevel.updateAndGet(newDetailLevel -> Math.max(newDetailLevel, pos.sectionDetailLevel));
|
||||
// This is a CAS with expected null value.
|
||||
RenderMetaDataFile metaFileCas = this.filesBySectionPos.putIfAbsent(pos, metaFile);
|
||||
return metaFileCas == null ? metaFile : metaFileCas;
|
||||
@@ -336,7 +336,10 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
RenderMetaDataFile metaFile = this.getLoadOrMakeFile(pos, true);
|
||||
|
||||
// On error, (when it returns null,) return an empty render source
|
||||
if (metaFile == null) return CompletableFuture.completedFuture(ColumnRenderSource.createEmptyRenderSource(pos));
|
||||
if (metaFile == null)
|
||||
{
|
||||
return CompletableFuture.completedFuture(ColumnRenderSource.createEmptyRenderSource(pos));
|
||||
}
|
||||
|
||||
CompletableFuture<ColumnRenderSource> future = metaFile.loadOrGetCachedDataSourceAsync(this.fileHandlerThreadPool, this.level).handle(
|
||||
(renderSource, exception) ->
|
||||
@@ -348,8 +351,10 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
|
||||
|
||||
return (renderSource != null) ? renderSource : ColumnRenderSource.createEmptyRenderSource(pos);
|
||||
});
|
||||
synchronized (taskTracker) {
|
||||
taskTracker.put(future, TaskType.Read);
|
||||
|
||||
synchronized (this.taskTracker)
|
||||
{
|
||||
this.taskTracker.put(future, TaskType.Read);
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
@@ -11,12 +11,18 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* The position object used to define LOD objects in the quad trees. <br>
|
||||
* The position object used to define LOD objects in the quad trees. <br><br>
|
||||
*
|
||||
* A section contains 64 x 64 LOD columns at a given quality.
|
||||
* The Section detail level is different from the LOD detail level.
|
||||
* For the specifics of how they compare can be viewed in the constants {@link #SECTION_BLOCK_DETAIL_LEVEL},
|
||||
* {@link #SECTION_CHUNK_DETAIL_LEVEL}, and {@link #SECTION_REGION_DETAIL_LEVEL}).
|
||||
* {@link #SECTION_CHUNK_DETAIL_LEVEL}, and {@link #SECTION_REGION_DETAIL_LEVEL}).<br><br>
|
||||
*
|
||||
* <strong>Why does the smallest render section represent 2x2 MC chunks (section detail level 6)? </strong> <br>
|
||||
* A section defines what unit the quad tree works in, because of that we don't want that unit to be too big or too small. <br>
|
||||
* <strong>Too small</strong>, and we'll have 1,000s of sections running around, all needing individual files and render buffers.<br>
|
||||
* <strong>Too big</strong>, and the LOD dropoff will be very noticeable.<br>
|
||||
* With those thoughts in mind we decided on a smallest section size of 32 data points square (IE 2x2 chunks).
|
||||
*
|
||||
* @author Leetom
|
||||
* @version 2022-11-6
|
||||
|
||||
@@ -237,7 +237,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
|
||||
{
|
||||
// FIXME having world generation enabled in a pre-generated world that doesn't have any DH data can cause this to happen
|
||||
// surprisingly reloadPos() doesn't appear to be the culprit, maybe there is an issue with reloading/changing the full data source?
|
||||
LOGGER.warn("Potential QuadTree concurrency issue. All child sections should be enabled and ready to render for pos: "+sectionPos);
|
||||
//LOGGER.warn("Potential QuadTree concurrency issue. All child sections should be enabled and ready to render for pos: "+sectionPos);
|
||||
}
|
||||
|
||||
// this section is now being rendered via its children
|
||||
@@ -254,17 +254,21 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
|
||||
// wait for the parent to disable before enabling this section, so we don't overdraw/overlap render sections
|
||||
if (!parentRenderSectionIsEnabled && renderSection.canRenderNow())
|
||||
{
|
||||
renderSection.enableRendering();
|
||||
|
||||
// delete/disable children, all of them will be a lower detail level than requested
|
||||
quadNode.deleteAllChildren((childRenderSection) ->
|
||||
// if rendering is already enabled we don't have to re-enable it
|
||||
if (!renderSection.isRenderingEnabled())
|
||||
{
|
||||
if (childRenderSection != null)
|
||||
renderSection.enableRendering();
|
||||
|
||||
// delete/disable children, all of them will be a lower detail level than requested
|
||||
quadNode.deleteAllChildren((childRenderSection) ->
|
||||
{
|
||||
childRenderSection.disableRendering();
|
||||
childRenderSection.disposeRenderData();
|
||||
}
|
||||
});
|
||||
if (childRenderSection != null)
|
||||
{
|
||||
childRenderSection.disableRendering();
|
||||
childRenderSection.disposeRenderData();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return renderSection.canRenderNow();
|
||||
}
|
||||
|
||||
@@ -60,18 +60,25 @@ public class LodRenderSection implements IDebugRenderable
|
||||
|
||||
/** a reference is used so the render buffer can be swapped to and from the buffer builder */
|
||||
public final AtomicReference<ColumnRenderBuffer> activeRenderBufferRef = new AtomicReference<>();
|
||||
private volatile boolean doDisposeActiveBuffer = false;
|
||||
private volatile boolean disposeActiveBuffer = false;
|
||||
|
||||
private final QuadTree<LodRenderSection> parentQuadTree;
|
||||
|
||||
public LodRenderSection(QuadTree<LodRenderSection> parentQuadTree, DhSectionPos pos) {
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public LodRenderSection(QuadTree<LodRenderSection> parentQuadTree, DhSectionPos pos)
|
||||
{
|
||||
this.pos = pos;
|
||||
this.parentQuadTree = parentQuadTree;
|
||||
|
||||
DebugRenderer.register(this);
|
||||
}
|
||||
|
||||
public void debugRender(DebugRenderer r)
|
||||
public void debugRender(DebugRenderer debugRenderer)
|
||||
{
|
||||
Color color = Color.red;
|
||||
|
||||
@@ -86,7 +93,7 @@ public class LodRenderSection implements IDebugRenderable
|
||||
if (canRenderNow() && isRenderingEnabled) color = Color.green;
|
||||
}
|
||||
|
||||
r.renderBox(new DebugRenderer.Box(this.pos, 400, 8f, Objects.hashCode(this), 0.1f, color));
|
||||
debugRenderer.renderBox(new DebugRenderer.Box(this.pos, 400, 8f, Objects.hashCode(this), 0.1f, color));
|
||||
}
|
||||
|
||||
|
||||
@@ -95,32 +102,30 @@ public class LodRenderSection implements IDebugRenderable
|
||||
// rendering //
|
||||
//===========//
|
||||
|
||||
public void enableRendering() {
|
||||
public void enableRendering()
|
||||
{
|
||||
// FIXME this is a temporary fix for sections not building the first time,
|
||||
// this may cause LODs to flash when first loading
|
||||
// Problem reproduction steps:
|
||||
// 1. connect to a multiplayer server
|
||||
// 2. enter spectator
|
||||
// 3. fly in one direction until section detail levels 7 and 8 appear
|
||||
// 4. empty LODs should appear
|
||||
if (!this.isRenderingEnabled)
|
||||
{
|
||||
// this only needs to be called when first enabling the section
|
||||
this.markBufferDirty();
|
||||
}
|
||||
|
||||
this.isRenderingEnabled = true;
|
||||
}
|
||||
public void disableRendering() {
|
||||
this.isRenderingEnabled = false;
|
||||
}
|
||||
public void disableRendering() { this.isRenderingEnabled = false; }
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// render data //
|
||||
//=============//
|
||||
|
||||
private void startLoadRenderSource() {
|
||||
this.renderSourceLoadFuture = this.renderSourceProvider.readAsync(this.pos);
|
||||
this.renderSourceLoadFuture.whenComplete((renderSource, ex) ->
|
||||
{
|
||||
this.renderSourceLoadFuture = null;
|
||||
this.renderSource = renderSource;
|
||||
this.lastNs = -1;
|
||||
markBufferDirty();
|
||||
if (this.reloadRenderSourceOnceLoaded)
|
||||
{
|
||||
this.reloadRenderSourceOnceLoaded = false;
|
||||
reload(this.renderSourceProvider);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** does nothing if a render source is already loaded or in the process of loading */
|
||||
public void loadRenderSource(ILodRenderSourceProvider renderDataProvider, IDhClientLevel level)
|
||||
@@ -129,6 +134,7 @@ public class LodRenderSection implements IDebugRenderable
|
||||
this.level = level;
|
||||
if (this.renderSourceProvider == null)
|
||||
{
|
||||
LOGGER.warn("LodRenderSection ["+this.pos+"] called loadRenderSource with a empty source provider");
|
||||
return;
|
||||
}
|
||||
// don't re-load or double load the render source
|
||||
@@ -136,23 +142,31 @@ public class LodRenderSection implements IDebugRenderable
|
||||
{
|
||||
return;
|
||||
}
|
||||
startLoadRenderSource();
|
||||
|
||||
this.startLoadRenderSourceAsync();
|
||||
}
|
||||
|
||||
public void reload(ILodRenderSourceProvider renderDataProvider)
|
||||
{
|
||||
if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
|
||||
// debug rendering
|
||||
if (this.pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
|
||||
{
|
||||
DebugRenderer.makeParticle(
|
||||
new DebugRenderer.BoxParticle(
|
||||
new DebugRenderer.Box(pos, 0, 256f, 0.03f, Color.cyan),
|
||||
0.5, 512f
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
this.renderSourceProvider = renderDataProvider;
|
||||
if (this.renderSourceProvider == null)
|
||||
{
|
||||
LOGGER.warn("LodRenderSection ["+this.pos+"] called reload with a empty source provider");
|
||||
return;
|
||||
}
|
||||
|
||||
// don't accidentally enable rendering for a disabled section
|
||||
if (!this.isRenderingEnabled)
|
||||
{
|
||||
@@ -161,16 +175,33 @@ public class LodRenderSection implements IDebugRenderable
|
||||
// wait for the current load future to finish before re-loading
|
||||
if (this.renderSourceLoadFuture != null)
|
||||
{
|
||||
reloadRenderSourceOnceLoaded = true;
|
||||
this.reloadRenderSourceOnceLoaded = true;
|
||||
return;
|
||||
}
|
||||
startLoadRenderSource();
|
||||
|
||||
this.startLoadRenderSourceAsync();
|
||||
}
|
||||
|
||||
private void startLoadRenderSourceAsync()
|
||||
{
|
||||
this.renderSourceLoadFuture = this.renderSourceProvider.readAsync(this.pos);
|
||||
this.renderSourceLoadFuture.whenComplete((renderSource, ex) ->
|
||||
{
|
||||
this.renderSource = renderSource;
|
||||
this.lastNs = -1;
|
||||
this.markBufferDirty();
|
||||
if (this.reloadRenderSourceOnceLoaded)
|
||||
{
|
||||
this.reloadRenderSourceOnceLoaded = false;
|
||||
this.reload(this.renderSourceProvider);
|
||||
}
|
||||
|
||||
this.renderSourceLoadFuture = null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//========================//
|
||||
// getters and properties //
|
||||
//========================//
|
||||
@@ -182,6 +213,12 @@ public class LodRenderSection implements IDebugRenderable
|
||||
|
||||
public boolean canRenderNow()
|
||||
{
|
||||
if (this.renderSourceLoadFuture != null || this.buildRenderBufferFuture != null)
|
||||
{
|
||||
// wait for loading to finish
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.renderSource != null
|
||||
&&
|
||||
(
|
||||
@@ -210,46 +247,44 @@ public class LodRenderSection implements IDebugRenderable
|
||||
this.buildRenderBufferFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBufferOutdated() {
|
||||
//if (this.lastNs == -1) return false;
|
||||
/* boolean inTimeout = System.nanoTime() - this.lastNs < SWAP_TIMEOUT_IN_NS;
|
||||
if (!inTimeout && ColumnRenderBufferBuilder.isBusy()) {
|
||||
this.lastNs += (long) (SWAP_BUSY_COLLISION_TIMEOUT_IN_NS * Math.random());
|
||||
return true;
|
||||
}*/
|
||||
return neighborUpdated || renderSource.localVersion.get() - lastSwapLocalVersion > 0;
|
||||
}
|
||||
|
||||
|
||||
private LodRenderSection[] getNeighbors()
|
||||
{
|
||||
LodRenderSection[] adjacents = new LodRenderSection[EDhDirection.ADJ_DIRECTIONS.length];
|
||||
for (EDhDirection direction : EDhDirection.ADJ_DIRECTIONS) {
|
||||
try {
|
||||
DhSectionPos adjPos = pos.getAdjacentPos(direction);
|
||||
LodRenderSection adjRenderSection = parentQuadTree.getValue(adjPos);
|
||||
LodRenderSection[] adjacentRenderSections = new LodRenderSection[EDhDirection.ADJ_DIRECTIONS.length];
|
||||
for (EDhDirection direction : EDhDirection.ADJ_DIRECTIONS)
|
||||
{
|
||||
try
|
||||
{
|
||||
DhSectionPos adjPos = this.pos.getAdjacentPos(direction);
|
||||
LodRenderSection adjRenderSection = this.parentQuadTree.getValue(adjPos);
|
||||
// adjacent render sources might be null
|
||||
adjacents[direction.ordinal() - 2] = adjRenderSection;
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
adjacentRenderSections[direction.ordinal() - 2] = adjRenderSection;
|
||||
}
|
||||
catch (IndexOutOfBoundsException e)
|
||||
{
|
||||
// adjacent positions can be out of bounds, in that case a null render source will be used
|
||||
}
|
||||
}
|
||||
return adjacents;
|
||||
|
||||
return adjacentRenderSections;
|
||||
}
|
||||
|
||||
private void tellNeighborsUpdated()
|
||||
{
|
||||
LodRenderSection[] adjacents = getNeighbors();
|
||||
for (LodRenderSection adj : adjacents) {
|
||||
if (adj != null) {
|
||||
LodRenderSection[] adjacentRenderSections = this.getNeighbors();
|
||||
for (LodRenderSection adj : adjacentRenderSections)
|
||||
{
|
||||
if (adj != null)
|
||||
{
|
||||
adj.neighborUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @return true if this section is loaded and set to render */
|
||||
public boolean canBuildBuffer() { return this.renderSource != null && this.buildRenderBufferFuture == null && !this.renderSource.isEmpty() && isBufferOutdated(); }
|
||||
|
||||
public boolean canBuildBuffer() { return this.renderSource != null && this.buildRenderBufferFuture == null && !this.renderSource.isEmpty() && this.isBufferOutdated(); }
|
||||
private boolean isBufferOutdated() { return this.neighborUpdated || (this.renderSource.localVersion.get() - this.lastSwapLocalVersion) > 0; }
|
||||
|
||||
/** @return true if this section is loaded and set to render */
|
||||
public boolean canSwapBuffer() { return this.buildRenderBufferFuture != null && this.buildRenderBufferFuture.isDone(); }
|
||||
|
||||
@@ -262,49 +297,71 @@ public class LodRenderSection implements IDebugRenderable
|
||||
*/
|
||||
public boolean tryBuildAndSwapBuffer()
|
||||
{
|
||||
if (doDisposeActiveBuffer && this.activeRenderBufferRef.get() != null) {
|
||||
doDisposeActiveBuffer = false;
|
||||
// delete the existing buffer if it should be disposed
|
||||
if (this.disposeActiveBuffer && this.activeRenderBufferRef.get() != null)
|
||||
{
|
||||
this.disposeActiveBuffer = false;
|
||||
this.activeRenderBufferRef.getAndSet(null).close();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// attempt to build the buffer
|
||||
boolean didSwapped = false;
|
||||
if (canBuildBuffer()) {
|
||||
//if (false)
|
||||
if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
|
||||
if (this.canBuildBuffer())
|
||||
{
|
||||
// debug
|
||||
if (this.pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
|
||||
{
|
||||
DebugRenderer.makeParticle(
|
||||
new DebugRenderer.BoxParticle(
|
||||
new DebugRenderer.Box(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
|
||||
)
|
||||
);
|
||||
neighborUpdated = false;
|
||||
long newVs = renderSource.localVersion.get();
|
||||
if (lastSwapLocalVersion != newVs) {
|
||||
lastSwapLocalVersion = newVs;
|
||||
tellNeighborsUpdated();
|
||||
}
|
||||
LodRenderSection[] adjacents = getNeighbors();
|
||||
|
||||
|
||||
this.neighborUpdated = false;
|
||||
long newVersion = this.renderSource.localVersion.get();
|
||||
if (this.lastSwapLocalVersion != newVersion)
|
||||
{
|
||||
this.lastSwapLocalVersion = newVersion;
|
||||
this.tellNeighborsUpdated();
|
||||
}
|
||||
|
||||
|
||||
LodRenderSection[] adjacentRenderSections = this.getNeighbors();
|
||||
ColumnRenderSource[] adjacentSources = new ColumnRenderSource[EDhDirection.ADJ_DIRECTIONS.length];
|
||||
for (int i = 0; i < EDhDirection.ADJ_DIRECTIONS.length; i++) {
|
||||
LodRenderSection adj = adjacents[i];
|
||||
if (adj != null) {
|
||||
for (int i = 0; i < EDhDirection.ADJ_DIRECTIONS.length; i++)
|
||||
{
|
||||
LodRenderSection adj = adjacentRenderSections[i];
|
||||
if (adj != null)
|
||||
{
|
||||
adjacentSources[i] = adj.getRenderSource();
|
||||
}
|
||||
}
|
||||
this.buildRenderBufferFuture = ColumnRenderBufferBuilder.buildBuffers(level, this.inactiveRenderBufferRef, renderSource, adjacentSources);
|
||||
|
||||
this.buildRenderBufferFuture = ColumnRenderBufferBuilder.buildBuffersAsync(this.level, this.inactiveRenderBufferRef, this.renderSource, adjacentSources);
|
||||
}
|
||||
if (canSwapBuffer()) {
|
||||
|
||||
|
||||
// attempt to swap in the buffer
|
||||
if (this.canSwapBuffer())
|
||||
{
|
||||
this.lastNs = System.nanoTime();
|
||||
ColumnRenderBuffer newBuffer;
|
||||
try {
|
||||
try
|
||||
{
|
||||
newBuffer = this.buildRenderBufferFuture.getNow(null);
|
||||
this.buildRenderBufferFuture = null;
|
||||
if (newBuffer == null) {
|
||||
if (newBuffer == null)
|
||||
{
|
||||
// failed.
|
||||
markBufferDirty();
|
||||
this.markBufferDirty();
|
||||
return false;
|
||||
}
|
||||
LodUtil.assertTrue(newBuffer.buffersUploaded, "The buffer future for "+pos+" returned an un-built buffer.");
|
||||
|
||||
LodUtil.assertTrue(newBuffer.buffersUploaded, "The buffer future for "+this.pos+" returned an un-built buffer.");
|
||||
ColumnRenderBuffer oldBuffer = this.activeRenderBufferRef.getAndSet(newBuffer);
|
||||
if (oldBuffer != null)
|
||||
{
|
||||
@@ -315,23 +372,33 @@ public class LodRenderSection implements IDebugRenderable
|
||||
didSwapped = true;
|
||||
LodUtil.assertTrue(swapped == null);
|
||||
}
|
||||
catch (CancellationException e1) {
|
||||
catch (CancellationException e1)
|
||||
{
|
||||
// ignore.
|
||||
this.buildRenderBufferFuture = null;
|
||||
}
|
||||
catch (CompletionException e) {
|
||||
LOGGER.error("Unable to get render buffer for "+pos+".", e);
|
||||
catch (CompletionException e)
|
||||
{
|
||||
LOGGER.error("Unable to get render buffer for " + pos + ".", e);
|
||||
this.buildRenderBufferFuture = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.buildRenderBufferFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
return didSwapped;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// base methods //
|
||||
//==============//
|
||||
|
||||
public String toString() {
|
||||
public String toString()
|
||||
{
|
||||
return "LodRenderSection{" +
|
||||
"pos=" + this.pos +
|
||||
", lodRenderSource=" + this.renderSource +
|
||||
@@ -340,17 +407,19 @@ public class LodRenderSection implements IDebugRenderable
|
||||
'}';
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
disposeRenderData();
|
||||
public void dispose()
|
||||
{
|
||||
this.disposeRenderData();
|
||||
DebugRenderer.unregister(this);
|
||||
if (doDisposeActiveBuffer && this.activeRenderBufferRef.get() != null) {
|
||||
if (this.disposeActiveBuffer && this.activeRenderBufferRef.get() != null)
|
||||
{
|
||||
this.activeRenderBufferRef.get().close();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void disposeRenderData() // synchronized is a band-aid solution to prevent a rare bug where the future isn't canceled in the right order
|
||||
{
|
||||
disposeRenderBuffer();
|
||||
this.disposeRenderBuffer();
|
||||
this.renderSource = null;
|
||||
if (this.renderSourceLoadFuture != null)
|
||||
{
|
||||
@@ -361,11 +430,10 @@ public class LodRenderSection implements IDebugRenderable
|
||||
|
||||
public void disposeRenderBuffer()
|
||||
{
|
||||
cancelBuildBuffer();
|
||||
doDisposeActiveBuffer = true;
|
||||
this.cancelBuildBuffer();
|
||||
this.disposeActiveBuffer = true;
|
||||
}
|
||||
|
||||
public void markBufferDirty() {
|
||||
lastSwapLocalVersion = -1;
|
||||
}
|
||||
public void markBufferDirty() { this.lastSwapLocalVersion = -1; }
|
||||
|
||||
}
|
||||
|
||||
@@ -156,17 +156,22 @@ public class RenderBufferHandler
|
||||
|
||||
DhSectionPos sectionPos = node.sectionPos;
|
||||
LodRenderSection renderSection = node.value;
|
||||
try {
|
||||
try
|
||||
{
|
||||
|
||||
if (renderSection != null) {
|
||||
if (rebuildAllBuffers) {
|
||||
if (renderSection != null)
|
||||
{
|
||||
if (rebuildAllBuffers)
|
||||
{
|
||||
renderSection.markBufferDirty();
|
||||
}
|
||||
renderSection.tryBuildAndSwapBuffer();
|
||||
|
||||
if (renderSection.isRenderingEnabled()) {
|
||||
if (renderSection.isRenderingEnabled())
|
||||
{
|
||||
AbstractRenderBuffer buffer = renderSection.activeRenderBufferRef.get();
|
||||
if (buffer != null) {
|
||||
if (buffer != null)
|
||||
{
|
||||
this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(buffer, sectionPos));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +261,11 @@
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.lodBias":
|
||||
"LOD Bias §6(Affects vanilla terrain)§r",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.lodBias.@tooltip":
|
||||
"Sets vanilla's lod bias value\nPlease press F3+T to reload the texture packs and apply this affect",
|
||||
"Sets vanilla's lod bias value\nPlease press F3+T to reload the texture packs and apply this affect.",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.lodShading":
|
||||
"LOD Shading",
|
||||
"distanthorizons.config.client.advanced.graphics.advancedGraphics.lodShading.@tooltip":
|
||||
"Defines how LODs should be shaded. \nCan be used to improve shader compatibility.",
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced.worldGenerator":
|
||||
@@ -726,5 +730,13 @@
|
||||
"distanthorizons.config.enum.EBufferRebuildTimes.NORMAL":
|
||||
"Normal",
|
||||
"distanthorizons.config.enum.EBufferRebuildTimes.RARE":
|
||||
"Rare"
|
||||
"Rare",
|
||||
|
||||
"distanthorizons.config.enum.ELodShading.MINECRAFT":
|
||||
"Minecraft",
|
||||
"distanthorizons.config.enum.ELodShading.OLD_LIGHTING":
|
||||
"Old Lighting",
|
||||
"distanthorizons.config.enum.ELodShading.NONE":
|
||||
"None"
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user