From 3179ba0db839260a084ad94f33f3039c4c25b02f Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 31 Jul 2023 20:43:35 -0500 Subject: [PATCH 1/9] refactor and comment --- .../ColumnRenderBufferBuilder.java | 4 +- .../renderfile/RenderSourceFileHandler.java | 17 +- .../core/render/LodRenderSection.java | 239 +++++++++++------- .../core/render/RenderBufferHandler.java | 15 +- 4 files changed, 171 insertions(+), 104 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java index bf3652f13..b1e73a0fb 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java @@ -53,7 +53,9 @@ public class ColumnRenderBufferBuilder // vbo building // //==============// - public static CompletableFuture buildBuffers(IDhClientLevel clientLevel, Reference renderBufferRef, ColumnRenderSource renderSource, ColumnRenderSource[] adjData) + public static CompletableFuture buildBuffersAsync( + IDhClientLevel clientLevel, Reference renderBufferRef, + ColumnRenderSource renderSource, ColumnRenderSource[] adjData) { /* if (isBusy()) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java index 61455e81a..75aa3f46e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java @@ -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 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; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java index 4803e0f58..45ec19aa6 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java @@ -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 activeRenderBufferRef = new AtomicReference<>(); - private volatile boolean doDisposeActiveBuffer = false; + private volatile boolean disposeActiveBuffer = false; private final QuadTree parentQuadTree; - public LodRenderSection(QuadTree parentQuadTree, DhSectionPos pos) { + + + //=============// + // constructor // + //=============// + + public LodRenderSection(QuadTree 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,17 @@ public class LodRenderSection implements IDebugRenderable // rendering // //===========// - public void enableRendering() { + public void enableRendering() + { 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 +121,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 +129,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 +162,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 +200,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 +234,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 +284,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 +359,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 +394,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 +417,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; } + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/RenderBufferHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderBufferHandler.java index eed83d04f..6a4f063dd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/RenderBufferHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderBufferHandler.java @@ -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)); } } From 8f6ee82fc7a078833b5daf5c566d59aa7f515a4c Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 31 Jul 2023 20:51:40 -0500 Subject: [PATCH 2/9] temporary fix for LodRenderSections not appearing --- .../core/render/LodQuadTree.java | 22 +++++++++++-------- .../core/render/LodRenderSection.java | 13 +++++++++++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java index 275dfd660..4aa0b7972 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java @@ -254,17 +254,21 @@ public class LodQuadTree extends QuadTree 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(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java index 45ec19aa6..a5c05de82 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java @@ -104,6 +104,19 @@ public class LodRenderSection implements IDebugRenderable 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; } From d9ef3670faabb299bff891dd5f8efca567fea6a7 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 31 Jul 2023 21:44:16 -0500 Subject: [PATCH 3/9] Slightly increase the default world gen thread count and activities --- .../presets/ThreadPresetConfigEventHandler.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java index 7d4916c2c..1323e8c09 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java @@ -18,27 +18,28 @@ 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 worldGenThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads, new HashMap() {{ this.put(EThreadPreset.MINIMAL_IMPACT, 1); this.put(EThreadPreset.LOW_IMPACT, getWorldGenDefaultThreadCount()); - this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.2)); + this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.3)); this.put(EThreadPreset.AGGRESSIVE, getThreadCountByPercent(0.4)); 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 worldGenRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads, new HashMap() {{ - 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.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); }}); From 61c8288b99d45cadf75790bac9ace99e1d3b85dd Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 1 Aug 2023 07:27:25 -0500 Subject: [PATCH 4/9] Remove thread preset "paid for whole cpu" and tweak CPU preset options --- .../config/quickOptions/EThreadPreset.java | 4 +- .../distanthorizons/core/config/Config.java | 4 +- .../ThreadPresetConfigEventHandler.java | 50 +++++++++---------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/api/src/main/java/com/seibel/distanthorizons/api/enums/config/quickOptions/EThreadPreset.java b/api/src/main/java/com/seibel/distanthorizons/api/enums/config/quickOptions/EThreadPreset.java index cf08b58c3..87e9f09ab 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/enums/config/quickOptions/EThreadPreset.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/enums/config/quickOptions/EThreadPreset.java @@ -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; } \ No newline at end of file diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index 3180916c4..de463329d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -833,7 +833,7 @@ public class Config public static final ConfigEntry numberOfDataTransformerThreads = new ConfigEntry.Builder() .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" @@ -848,7 +848,7 @@ public class Config + THREAD_NOTE) .build(); public static final ConfigEntry runTimeRatioForDataTransformerThreads = new ConfigEntry.Builder() - .setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getDataConverterDefaultRunTimeRatio(), 1.0) + .setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getDataTransformerDefaultRunTimeRatio(), 1.0) .comment(THREAD_RUN_TIME_RATIO_NOTE) .build(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java index 1323e8c09..1a3f5e295 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/ThreadPresetConfigEventHandler.java @@ -18,7 +18,7 @@ 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); + private static final boolean LOW_THREAD_COUNT_CPU = (Runtime.getRuntime().availableProcessors() <= 4); @@ -28,9 +28,9 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan {{ this.put(EThreadPreset.MINIMAL_IMPACT, 1); this.put(EThreadPreset.LOW_IMPACT, getWorldGenDefaultThreadCount()); - this.put(EThreadPreset.BALANCED, getThreadCountByPercent(0.3)); - 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 LOW_THREAD_COUNT_CPU ? 0.5 : 1; } private final ConfigEntryWithPresetOptions worldGenRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads, @@ -40,7 +40,7 @@ public class ThreadPresetConfigEventHandler extends AbstractPresetConfigEventHan this.put(EThreadPreset.LOW_IMPACT, getWorldGenDefaultRunTimeRatio()); 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); + //this.put(EThreadPreset.I_PAID_FOR_THE_WHOLE_CPU, 1.0); }}); @@ -52,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 bufferBuilderRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForBufferBuilderThreads, new HashMap() {{ 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); }}); @@ -74,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 fileHandlerRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForFileHandlerThreads, @@ -84,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 dataTransformerThreadCount = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.numberOfDataTransformerThreads, new HashMap() {{ 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 dataTransformerRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForDataTransformerThreads, new HashMap() {{ - 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); }}); @@ -118,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 chunkLodConverterRunTime = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.MultiThreading.runTimeRatioForChunkLodConverterThreads, new HashMap() {{ - 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); }}); From 8daa52f36e7570069cb05db3d0a285f1ccaa8296 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 1 Aug 2023 07:42:12 -0500 Subject: [PATCH 5/9] Add a 1 sec timeout before applying config presets --- .../AbstractPresetConfigEventHandler.java | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java index 2cfb8d104..9c21d6233 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java @@ -12,9 +12,11 @@ import java.util.*; public abstract class AbstractPresetConfigEventHandler> implements IConfigListener { private static final Logger LOGGER = LogManager.getLogger(); - + private static final long MS_DELAY_BEFORE_APPLYING_PRESET = 1_000; protected final ArrayList> 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 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 */ } From 5f681223364c49f3be8900ede0cc8c677aa7dd1b Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 1 Aug 2023 07:44:48 -0500 Subject: [PATCH 6/9] temporarily comment out LodQuadTree concurrency warning The warning doesn't appear to be causing any ill effects for now, but it will still need to be fixed in the future --- .../com/seibel/distanthorizons/core/render/LodQuadTree.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java index 4aa0b7972..d2636d04b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java @@ -237,7 +237,7 @@ public class LodQuadTree extends QuadTree 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 From 66d3fc8151af72e72cd66dd5bdd991bd1d022d78 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 1 Aug 2023 07:51:42 -0500 Subject: [PATCH 7/9] Add a note to DhSectionPos about why a section is 2x2 chunks --- .../seibel/distanthorizons/core/pos/DhSectionPos.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java b/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java index b0d7bfdf2..8a2f94085 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java @@ -8,12 +8,18 @@ import org.jetbrains.annotations.Nullable; import java.util.function.Consumer; /** - * The position object used to define LOD objects in the quad trees.
+ * The position object used to define LOD objects in the quad trees.

* * 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}).

+ * + * Why does the smallest render section represent 2x2 MC chunks (section detail level 6)?
+ * 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.
+ * Too small, and we'll have 1,000s of sections running around, all needing individual files and render buffers.
+ * Too big, and the LOD dropoff will be very noticeable.
+ * 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 From 622a1633fa38289284faaad93bf2893899cf2dae Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 1 Aug 2023 20:11:21 -0500 Subject: [PATCH 8/9] Add config for Lod Shading so Old Lighting can be enabled for shaders --- .../api/enums/config/ELodShading.java | 37 +++++++++++++++++++ .../distanthorizons/core/config/Config.java | 14 +++++++ .../RenderCacheConfigEventHandler.java | 9 +++++ 3 files changed, 60 insertions(+) create mode 100644 api/src/main/java/com/seibel/distanthorizons/api/enums/config/ELodShading.java diff --git a/api/src/main/java/com/seibel/distanthorizons/api/enums/config/ELodShading.java b/api/src/main/java/com/seibel/distanthorizons/api/enums/config/ELodShading.java new file mode 100644 index 000000000..2c14a8874 --- /dev/null +++ b/api/src/main/java/com/seibel/distanthorizons/api/enums/config/ELodShading.java @@ -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 . + */ + +package com.seibel.distanthorizons.api.enums.config; + +/** + * MINECRAFT
+ * OLD_LIGHTING
+ * NONE
+ */ +public enum ELodShading +{ + // Reminder: + // when adding items up the API minor version + // when removing items up the API major version + + MINECRAFT, + OLD_LIGHTING, + NONE; + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index de463329d..4a8bba8af 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -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 lodShading = new ConfigEntry.Builder() + .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(); + } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/RenderCacheConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/RenderCacheConfigEventHandler.java index a88979cf2..8bbca7f0c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/RenderCacheConfigEventHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/RenderCacheConfigEventHandler.java @@ -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) From fc1bbd2f917dc0a3e15f594caabd14eb8c46485e Mon Sep 17 00:00:00 2001 From: James Seibel Date: Wed, 2 Aug 2023 07:04:30 -0500 Subject: [PATCH 9/9] Add missing Lod Shading localization items --- .../assets/distanthorizons/lang/en_us.json | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/assets/distanthorizons/lang/en_us.json b/core/src/main/resources/assets/distanthorizons/lang/en_us.json index 09091d2bc..c4da69c7b 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -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": @@ -722,5 +726,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" + }