This commit is contained in:
s809
2023-08-04 21:10:41 +05:00
13 changed files with 338 additions and 157 deletions
@@ -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;
}
@@ -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();
@@ -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)
@@ -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 */ }
@@ -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);
}});
@@ -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())
{
@@ -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"
}