From e0ad956e34174979e7ab59cd6b528a293d268e82 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 28 Jul 2024 20:18:15 -0500 Subject: [PATCH 01/27] Fix incorrect DhApiChunk create constructor parameter order (again) --- .../com/seibel/distanthorizons/api/objects/data/DhApiChunk.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java index 70535e5e8..bf74fabfd 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java @@ -66,7 +66,7 @@ public class DhApiChunk * @since API 3.0.0 */ public static DhApiChunk create(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos) - { return new DhApiChunk(chunkPosX, chunkPosZ, topYBlockPos, bottomYBlockPos, false); } + { return new DhApiChunk(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos, false); } /** * Only visible to internal DH methods From 8056a5b8bf7190878f6e76a747933d0b17121e11 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 29 Jul 2024 07:08:42 -0500 Subject: [PATCH 02/27] Add Buffer Builder/Uploader thread pools to the F3 menu --- .../com/seibel/distanthorizons/core/logging/f3/F3Screen.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java b/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java index b0e0f5449..6bbf41622 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java @@ -72,6 +72,8 @@ public class F3Screen ThreadPoolExecutor worldGenPool = ThreadPoolUtil.getWorldGenExecutor(); ThreadPoolExecutor fileHandlerPool = ThreadPoolUtil.getFileHandlerExecutor(); ThreadPoolExecutor updatePool = ThreadPoolUtil.getUpdatePropagatorExecutor(); + ThreadPoolExecutor bufferBuilderPool = ThreadPoolUtil.getBufferBuilderExecutor(); + ThreadPoolExecutor bufferUploaderPool = ThreadPoolUtil.getBufferUploaderExecutor(); AbstractDhWorld world = SharedApi.getAbstractDhWorld(); Iterable levelIterator = world.getAllLoadedLevels(); @@ -84,6 +86,8 @@ public class F3Screen messageList.add(getThreadPoolStatString("World Gen", worldGenPool));//"World Gen Tasks: 40/5304, (in progress: 7)"); messageList.add(getThreadPoolStatString("File Handler", fileHandlerPool)); messageList.add(getThreadPoolStatString("Update Propagator", updatePool)); + messageList.add(getThreadPoolStatString("Buffer Builder", bufferBuilderPool)); + messageList.add(getThreadPoolStatString("Buffer Uploader", bufferUploaderPool)); messageList.add(""); // chunk updates messageList.add(SharedApi.INSTANCE.getDebugMenuString()); From e9788acb46e6e07c94d35bf0e5210f73b660cd70 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 29 Jul 2024 07:13:06 -0500 Subject: [PATCH 03/27] Fix rapidly changing dimensions causing the game to crash --- .../ColumnRenderBufferBuilder.java | 52 ++++++-- .../core/render/LodRenderSection.java | 124 ++++++++++++------ 2 files changed, 126 insertions(+), 50 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 def8f252c..9276f135d 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 @@ -61,22 +61,21 @@ public class ColumnRenderBufferBuilder // vbo building // //==============// - /** @link adjData should be null for adjacent sections that cross detail level boundaries */ - public static CompletableFuture buildAndUploadBuffersAsync( + public static CompletableFuture buildBuffersAsync( IDhClientLevel clientLevel, - ColumnRenderSource renderSource, ColumnRenderSource[] adjData) + ColumnRenderSource renderSource, ColumnRenderSource[] adjData + ) { ThreadPoolExecutor bufferBuilderExecutor = ThreadPoolUtil.getBufferBuilderExecutor(); - ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor(); - if ((bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated()) || - (bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated())) + if (bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated()) { // one or more of the thread pools has been shut down - CompletableFuture future = new CompletableFuture<>(); + CompletableFuture future = new CompletableFuture<>(); future.cancel(true); return future; } + try { return CompletableFuture.supplyAsync(() -> @@ -97,8 +96,39 @@ public class ColumnRenderBufferBuilder LOGGER.error("LodNodeBufferBuilder was unable to build quads for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: ["+ e3.getMessage()+"].", e3); throw e3; } - }, bufferBuilderExecutor) - .thenApplyAsync((quadBuilder) -> + }, bufferBuilderExecutor); + } + catch (RejectedExecutionException ignore) + { + // the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back + + CompletableFuture future = new CompletableFuture<>(); + future.cancel(true); + return future; + } + } + + /** @link adjData should be null for adjacent sections that cross detail level boundaries */ + public static CompletableFuture uploadBuffersAsync( + IDhClientLevel clientLevel, + ColumnRenderSource renderSource, + LodQuadBuilder quadBuilder + ) + { + // TODO put into a single future/thread so it can be easily canceled + ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor(); + if (bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated()) + { + // one or more of the thread pools has been shut down + CompletableFuture future = new CompletableFuture<>(); + future.cancel(true); + return future; + } + + + try + { + return CompletableFuture.supplyAsync(() -> { try { @@ -128,14 +158,14 @@ public class ColumnRenderBufferBuilder } catch (Throwable e3) { - LOGGER.error("LodNodeBufferBuilder was unable to upload buffer: " + e3.getMessage(), e3); + LOGGER.error("LodNodeBufferBuilder was unable to upload buffer for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: [" + e3.getMessage() + "].", e3); throw e3; } }, bufferUploaderExecutor); } catch (RejectedExecutionException ignore) { - // the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back + // shouldn't happen, but just in case CompletableFuture future = new CompletableFuture<>(); future.cancel(true); 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 8a58e76f5..d12fba56a 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 @@ -23,6 +23,7 @@ import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBufferBuilder; +import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodQuadBuilder; import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.enums.EDhDirection; @@ -80,7 +81,19 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable * Encapsulates everything between pulling data from the database (including neighbors) * up to the point when geometry data is uploaded to the GPU. */ - private CompletableFuture uploadRenderDataToGpuFuture = null; + private CompletableFuture buildAndUploadRenderDataToGpuFuture = null; + /** + * Represents just building the {@link LodQuadBuilder}.
+ * Separate from {@link LodRenderSection#bufferUploadFuture} because they run on + * different thread pools and need to be canceled separately. + */ + private CompletableFuture bufferBuildFuture = null; + /** + * Represents just uploading the {@link LodQuadBuilder} to the GPU.
+ * Separate from {@link LodRenderSection#bufferBuildFuture} because they run on + * different thread pools and need to be canceled separately. + */ + private CompletableFuture bufferUploadFuture = null; private final ReentrantLock getRenderSourceLock = new ReentrantLock(); /** Stored as a class variable so we can reuse it's result across multiple LOD loads if necessary */ @@ -123,7 +136,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable return; } - if (this.uploadRenderDataToGpuFuture != null) + if (this.buildAndUploadRenderDataToGpuFuture != null) { // don't accidentally queue multiple uploads at the same time return; @@ -137,7 +150,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable return; } - this.uploadRenderDataToGpuFuture = CompletableFuture.runAsync(() -> + this.buildAndUploadRenderDataToGpuFuture = CompletableFuture.runAsync(() -> { //==================// // load render data // @@ -145,15 +158,15 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable this.tryDecrementingLoadFutureArray(this.adjacentLoadRefFutures); - ReferencedFutureWrapper thisLoadFuture = this.getRenderSourceAsync(); - ReferencedFutureWrapper[] adjLoadRefFutures = this.getNeighborRenderSourcesAsync(); + ReferencedFutureWrapper thisRenderSourceLoadFuture = this.getRenderSourceAsync(); + ReferencedFutureWrapper[] adjRenderSourceLoadRefFutures = this.getNeighborRenderSourcesAsync(); // wait for all futures to complete together, // merging the futures makes loading significantly faster than loading this position then loading its neighbors ArrayList> futureList = new ArrayList<>(); - futureList.add(thisLoadFuture.future); - for (ReferencedFutureWrapper refFuture : adjLoadRefFutures) + futureList.add(thisRenderSourceLoadFuture.future); + for (ReferencedFutureWrapper refFuture : adjRenderSourceLoadRefFutures) { futureList.add(refFuture.future); } @@ -162,26 +175,26 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable { try { - ColumnRenderSource renderSource = thisLoadFuture.future.get(); + ColumnRenderSource renderSource = thisRenderSourceLoadFuture.future.get(); if (renderSource == null || renderSource.isEmpty()) { - thisLoadFuture.decrementRefCount(); - for (ReferencedFutureWrapper futureWrapper : adjLoadRefFutures) + thisRenderSourceLoadFuture.decrementRefCount(); + for (ReferencedFutureWrapper futureWrapper : adjRenderSourceLoadRefFutures) { futureWrapper.decrementRefCount(); } // nothing needs to be rendered this.canRender = false; - this.uploadRenderDataToGpuFuture = null; + this.buildAndUploadRenderDataToGpuFuture = null; return; } - //==============================// - // build/upload new render data // - //==============================// + //=======================// + // build new render data // + //=======================// try { @@ -190,45 +203,69 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable ColumnRenderSource[] adjacentRenderSections = new ColumnRenderSource[EDhDirection.ADJ_DIRECTIONS.length]; for (int i = 0; i < EDhDirection.ADJ_DIRECTIONS.length; i++) { - adjacentRenderSections[i] = adjLoadRefFutures[i].future.getNow(null); + adjacentRenderSections[i] = adjRenderSourceLoadRefFutures[i].future.getNow(null); } - ColumnRenderBufferBuilder.buildAndUploadBuffersAsync(this.level, renderSource, adjacentRenderSections) - .thenAccept((buffer) -> + + if (this.bufferBuildFuture != null) + { + // shouldn't normally happen, but just in case canceling the previous future + // prevents the CPU from working on something that won't be used + this.bufferBuildFuture.cancel(true); + } + this.bufferBuildFuture = ColumnRenderBufferBuilder.buildBuffersAsync(this.level, renderSource, adjacentRenderSections); + this.bufferBuildFuture.thenAccept((lodQuadBuilder) -> { - // upload complete, clean up the old data if - this.renderBuffer = buffer; - this.canRender = (buffer != null); - this.uploadRenderDataToGpuFuture = null; - if (previousBuffer != null) + + //===================================// + // upload new render data to the GPU // + //===================================// + + if (this.bufferUploadFuture != null) { - previousBuffer.close(); + // shouldn't normally happen, but just in case canceling the previous future + // prevents the CPU from working on something that won't be used + this.bufferUploadFuture.cancel(true); } - - thisLoadFuture.decrementRefCount(); - this.tryDecrementingLoadFutureArray(adjLoadRefFutures); - this.adjacentLoadRefFutures = null; + this.bufferUploadFuture = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, renderSource, lodQuadBuilder); + this.bufferUploadFuture.thenAccept((buffer) -> + { + // upload complete, clean up the old data if + this.renderBuffer = buffer; + this.canRender = (buffer != null); + this.buildAndUploadRenderDataToGpuFuture = null; + + + if (previousBuffer != null) + { + previousBuffer.close(); + } + + thisRenderSourceLoadFuture.decrementRefCount(); + this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures); + this.adjacentLoadRefFutures = null; + }); }); } catch (Exception e) { - thisLoadFuture.decrementRefCount(); - this.tryDecrementingLoadFutureArray(adjLoadRefFutures); + thisRenderSourceLoadFuture.decrementRefCount(); + this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures); this.adjacentLoadRefFutures = null; LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e); - this.uploadRenderDataToGpuFuture = null; + this.buildAndUploadRenderDataToGpuFuture = null; } } catch (Exception e) { - thisLoadFuture.decrementRefCount(); - this.tryDecrementingLoadFutureArray(adjLoadRefFutures); + thisRenderSourceLoadFuture.decrementRefCount(); + this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures); this.adjacentLoadRefFutures = null; LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e); - this.uploadRenderDataToGpuFuture = null; + this.buildAndUploadRenderDataToGpuFuture = null; } }); }, executor); @@ -324,8 +361,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable */ public void cancelGpuUpload() { - CompletableFuture future = this.uploadRenderDataToGpuFuture; - this.uploadRenderDataToGpuFuture = null; + CompletableFuture future = this.buildAndUploadRenderDataToGpuFuture; + this.buildAndUploadRenderDataToGpuFuture = null; if (future != null) { // interrupting the future speeds things up, but also causes @@ -342,7 +379,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable public boolean canRender() { return this.canRender; } - public boolean gpuUploadInProgress() { return this.uploadRenderDataToGpuFuture != null; } + public boolean gpuUploadInProgress() { return this.buildAndUploadRenderDataToGpuFuture != null; } @@ -452,9 +489,18 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable this.renderBuffer.close(); } - if (this.uploadRenderDataToGpuFuture != null) + // cancel all in-progress futures since they aren't needed any more + if (this.buildAndUploadRenderDataToGpuFuture != null) { - this.uploadRenderDataToGpuFuture.cancel(true); + this.buildAndUploadRenderDataToGpuFuture.cancel(true); + } + if (this.bufferBuildFuture != null) + { + this.bufferBuildFuture.cancel(true); + } + if (this.bufferUploadFuture != null) + { + this.bufferUploadFuture.cancel(true); } // this render section won't be rendering, we don't need to load any data for it @@ -489,7 +535,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable { color = Color.green; } - else if (this.uploadRenderDataToGpuFuture != null) + else if (this.buildAndUploadRenderDataToGpuFuture != null) { color = Color.yellow; } From 5588b0d2fa4861202f1c0da050d6c3878beeeba8 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 29 Jul 2024 07:29:48 -0500 Subject: [PATCH 04/27] Add TODO to cleanup LodRenderSection futures --- .../com/seibel/distanthorizons/core/render/LodRenderSection.java | 1 + 1 file changed, 1 insertion(+) 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 d12fba56a..f8ddf4dee 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 @@ -127,6 +127,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable // render data loading/uploading // //===============================// + // TODO cleanup, there's a lot of nested futures and duplicate error handling here and it's hard to read public synchronized void uploadRenderDataToGpuAsync() { if (!GLProxy.hasInstance()) From 79bfa21115100c093aff1d0574c6827c99f94402 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 29 Jul 2024 20:40:23 -0500 Subject: [PATCH 05/27] Fix default logging debug to file --- .../distanthorizons/core/config/Config.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 ec0ad8a45..a24289202 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 @@ -1140,63 +1140,63 @@ public class Config // TODO add change all option // TODO default to error chat and info file public static ConfigEntry logWorldGenEvent = new ConfigEntry.Builder() - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about the world generation process. \n" + "This can be useful for debugging.") .build(); public static ConfigEntry logWorldGenPerformance = new ConfigEntry.Builder() - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log performance about the world generation process. \n" + "This can be useful for debugging.") .build(); public static ConfigEntry logWorldGenLoadEvent = new ConfigEntry.Builder() - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about the world generation process. \n" + "This can be useful for debugging.") .build(); public static ConfigEntry logLodBuilderEvent = new ConfigEntry.Builder() - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about the LOD generation process. \n" + "This can be useful for debugging.") .build(); public static ConfigEntry logRendererBufferEvent = new ConfigEntry.Builder() - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about the renderer buffer process. \n" + "This can be useful for debugging.") .build(); public static ConfigEntry logRendererGLEvent = new ConfigEntry.Builder() - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about the renderer OpenGL process. \n" + "This can be useful for debugging.") .build(); public static ConfigEntry logFileReadWriteEvent = new ConfigEntry.Builder() - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about file read/write operations. \n" + "This can be useful for debugging.") .build(); public static ConfigEntry logFileSubDimEvent = new ConfigEntry.Builder() - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about file sub-dimension operations. \n" + "This can be useful for debugging.") .build(); public static ConfigEntry logNetworkEvent = new ConfigEntry.Builder() - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about network operations. \n" + "This can be useful for debugging.") From 66feb0b9c2842a82e337ce3b51d8bab8b72aa5ee Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 30 Jul 2024 15:35:01 -0500 Subject: [PATCH 06/27] Fix SSAO shader crashing when entering empty config values --- .../render/renderer/shaders/SSAOShader.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/shaders/SSAOShader.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/shaders/SSAOShader.java index 05834e159..a625e2696 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/shaders/SSAOShader.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/shaders/SSAOShader.java @@ -103,17 +103,19 @@ public class SSAOShader extends AbstractShaderRenderer this.shader.setUniform(this.gSampleCountUniform, Config.Client.Advanced.Graphics.Ssao.sampleCount.get()); - this.shader.setUniform(this.gRadiusUniform, - Config.Client.Advanced.Graphics.Ssao.radius.get().floatValue()); + // Implicit Number cast needs to be done to prevent issues with the default value being a int + Number radius = Config.Client.Advanced.Graphics.Ssao.radius.get(); + this.shader.setUniform(this.gRadiusUniform, radius.floatValue()); - this.shader.setUniform(this.gStrengthUniform, - Config.Client.Advanced.Graphics.Ssao.strength.get().floatValue()); - this.shader.setUniform(this.gMinLightUniform, - Config.Client.Advanced.Graphics.Ssao.minLight.get().floatValue()); + Number strength = Config.Client.Advanced.Graphics.Ssao.strength.get(); + this.shader.setUniform(this.gStrengthUniform, strength.floatValue()); - this.shader.setUniform(this.gBiasUniform, - Config.Client.Advanced.Graphics.Ssao.bias.get().floatValue()); + Number minLight = Config.Client.Advanced.Graphics.Ssao.minLight.get(); + this.shader.setUniform(this.gMinLightUniform, minLight.floatValue()); + + Number bias = Config.Client.Advanced.Graphics.Ssao.bias.get(); + this.shader.setUniform(this.gBiasUniform, bias.floatValue()); GL32.glActiveTexture(GL32.GL_TEXTURE0); GL32.glBindTexture(GL32.GL_TEXTURE_2D, LodRenderer.getActiveDepthTextureId()); From f1564cc90bf58bdfd4019c5d5723fc060754c3c6 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 30 Jul 2024 15:47:48 -0500 Subject: [PATCH 07/27] Fix presets only using "custom" after any value was changed --- .../eventHandlers/presets/AbstractPresetConfigEventHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5e8b9ce71..978f835ef 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 @@ -168,7 +168,7 @@ public abstract class AbstractPresetConfigEventHandler Date: Tue, 30 Jul 2024 17:00:59 -0500 Subject: [PATCH 08/27] Remove unused sodium and McRenderWrapper methods Removed methods were originally used to cull LODs if MC had loaded chunks, however this turned out to be more trouble than it was worth and caused more problems than it solved. --- .../distanthorizons/core/util/LodUtil.java | 39 +------ .../gridList/EdgeDistanceBooleanGrid.java | 100 ------------------ .../minecraft/IMinecraftRenderWrapper.java | 65 ------------ .../modAccessor/ISodiumAccessor.java | 2 - 4 files changed, 1 insertion(+), 205 deletions(-) delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/util/gridList/EdgeDistanceBooleanGrid.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/LodUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/LodUtil.java index ad9de0b90..8c652ebda 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/LodUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/LodUtil.java @@ -29,11 +29,8 @@ import java.util.concurrent.RejectedExecutionException; import com.seibel.distanthorizons.api.enums.config.EDhApiVanillaOverdraw; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; -import com.seibel.distanthorizons.core.pos.DhChunkPos; -import com.seibel.distanthorizons.core.pos.Pos2D; import com.seibel.distanthorizons.core.render.vertexFormat.DefaultLodVertexFormats; import com.seibel.distanthorizons.core.render.vertexFormat.LodVertexFormat; -import com.seibel.distanthorizons.core.util.gridList.EdgeDistanceBooleanGrid; import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; @@ -223,36 +220,6 @@ public class LodUtil return offset; } - /** not currently used since the new rendering system can't easily toggle single chunks to render */ - @Deprecated - public static EdgeDistanceBooleanGrid readVanillaRenderedChunks() - { - int offset = computeOverdrawOffset(); - if (offset == Integer.MAX_VALUE) return null; - int renderDist = MC_RENDER.getRenderDistance() + 1; - - Iterator posIter = MC_RENDER.getVanillaRenderedChunks().iterator(); - - return new EdgeDistanceBooleanGrid(new Iterator() - { - @Override - public boolean hasNext() - { - return posIter.hasNext(); - } - - @Override - public Pos2D next() - { - DhChunkPos pos = posIter.next(); - return new Pos2D(pos.x, pos.z); - } - }, - MC_CLIENT.getPlayerChunkPos().x - renderDist, - MC_CLIENT.getPlayerChunkPos().z - renderDist, - renderDist * 2 + 1); - } - /** Returns the chunk int position for the given double position */ public static int getChunkPosFromDouble(double value) { return (int) Math.floor(value / CHUNK_WIDTH); } /** Returns the float position inside the chunk for the given double position */ @@ -275,11 +242,6 @@ public class LodUtil return true; } - public static void checkInterrupts() throws InterruptedException - { - if (Thread.interrupted()) throw new InterruptedException(); - } - /** * Format a given string with params using log4j's MessageFormat * @@ -295,6 +257,7 @@ public class LodUtil return LOGGER.getMessageFactory().newMessage(str, param).getFormattedMessage(); } + // TODO move /** * Returns a shortened version of the given string that is no longer than maxLength.
* If null returns the empty string. diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/gridList/EdgeDistanceBooleanGrid.java b/core/src/main/java/com/seibel/distanthorizons/core/util/gridList/EdgeDistanceBooleanGrid.java deleted file mode 100644 index c6f3dbcd5..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/gridList/EdgeDistanceBooleanGrid.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * This file is part of the Distant Horizons mod - * licensed under the GNU LGPL v3 License. - * - * Copyright (C) 2020-2023 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.seibel.distanthorizons.core.util.gridList; - -import com.seibel.distanthorizons.core.pos.Pos2D; -import com.seibel.distanthorizons.core.util.objects.BoolType; - -import java.util.Iterator; -import java.util.function.IntPredicate; - -public class EdgeDistanceBooleanGrid extends PosArrayGridList -{ - ArrayGridList edgeCache = null; - - public EdgeDistanceBooleanGrid(Iterator posIter, int offsetX, int offsetY, int gridSize) - { - super(gridSize, offsetX, offsetY); - while (posIter.hasNext()) - { - Pos2D p = posIter.next(); - this.set(p, BoolType.TRUE); - } - } - - // Return false if it is indeed updated - private static boolean updatePos(ArrayGridList grid, int ox, int oy) - { - if (grid.get(ox, oy) < 0) return true; - if (ox == 0 || oy == 0 || ox == grid.gridSize - 1 || oy == grid.gridSize - 1) - { - return true; - } - - int v = grid.get(ox, oy); - if ( - grid.get(ox, oy + 1) < v || - grid.get(ox, oy - 1) < v || - grid.get(ox + 1, oy) < v || - grid.get(ox - 1, oy) < v - ) - { - return true; - } - else - { - grid.set(ox, oy, v + 1); - return false; - } - } - - //FIXME: This is slow and expensive. Use queue to make this skip recheck done pos - private void computeEdgeCache() - { - if (edgeCache != null) return; - - edgeCache = new ArrayGridList(gridSize, (ox, oy) -> { - BoolType b = get(ox + getOffsetX(), oy + getOffsetY()); - return b == null ? -1 : 0; - }); - - final boolean[] isDone = {false}; - while (!isDone[0]) - { - isDone[0] = true; - edgeCache.forEachPos((ox, oy) -> { - isDone[0] &= updatePos(edgeCache, ox, oy); - }); - } - } - - // 0 means right on the edge, while 1 means 1 ceil away. Uses Manhattan Distance - public > void flagAllWithDistance(T list, IntPredicate predicate) - { - computeEdgeCache(); - edgeCache.forEachPos((ox, oy) -> { - int v = edgeCache.get(ox, oy); - if (v < 0 || !predicate.test(v)) return; - list.set(ox + getOffsetX(), oy + getOffsetY(), BoolType.TRUE); - }); - } - - -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java index 2fc860772..538f8cc0f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java @@ -48,18 +48,10 @@ public interface IMinecraftRenderWrapper extends IBindable { Vec3f getLookAtVector(); - DhBlockPos getCameraBlockPosition(); - boolean playerHasBlindingEffect(); Vec3d getCameraExactPosition(); - Mat4f getWorldViewMatrix(); - - Mat4f getDefaultProjectionMatrix(float partialTicks); - - double getGamma(); - Color getFogColor(float partialTicks); default Color getSpecialFogColor(float partialTicks) { return getFogColor(partialTicks); } @@ -90,63 +82,6 @@ public interface IMinecraftRenderWrapper extends IBindable */ void clearTargetFrameBuffer(); - /** - * This method returns the ChunkPos of all chunks that Minecraft - * is going to render this frame. - *
- * If not implemented this calls {@link #getMaximumRenderedChunks()}. - */ - default HashSet getVanillaRenderedChunks() - { - // FIXME: Is this actually required? Does it make a differance if it exists or not? - ISodiumAccessor sodium = ModAccessorInjector.INSTANCE.get(ISodiumAccessor.class); - return sodium == null ? getMaximumRenderedChunks() : sodium.getNormalRenderedChunks(); - } - - static boolean correctedCheckRadius(int dx, int dz, int radius2Mul4) - { - dx = dx * 2;// + (dx < 0 ? -1 : 1); - dz = dz * 2;// + (dz < 0 ? -1 : 1); - return (dx * dx + dz * dz <= radius2Mul4); - } - - /** - * Doesn't need to be implemented.
- * Returns every chunk position within the vanilla render distance. - */ - default HashSet getMaximumRenderedChunks() - { - IMinecraftClientWrapper mcWrapper = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - IWrapperFactory factory = SingletonInjector.INSTANCE.get(IWrapperFactory.class); - IVersionConstants versionConstants = SingletonInjector.INSTANCE.get(IVersionConstants.class); - IMinecraftClientWrapper minecraft = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - ILevelWrapper clientWorld = minecraft.getWrappedClientLevel(); - - int chunkDist = this.getRenderDistance() + 1; // For some reason having '+1' is actually closer to real value - - DhChunkPos centerChunkPos = mcWrapper.getPlayerChunkPos(); - int centerChunkX = centerChunkPos.x; - int centerChunkZ = centerChunkPos.z; - int chunkDist2Mul4 = chunkDist * chunkDist * 4; - - // add every position within render distance - HashSet renderedPos = new HashSet(); - for (int deltaChunkX = -chunkDist; deltaChunkX <= chunkDist; deltaChunkX++) - { - for (int deltaChunkZ = -chunkDist; deltaChunkZ <= chunkDist; deltaChunkZ++) - { - if (!versionConstants.isVanillaRenderedChunkSquare() && - !correctedCheckRadius(deltaChunkX, deltaChunkZ, chunkDist2Mul4)) - { - continue; - } - if (!clientWorld.hasChunkLoaded(centerChunkX + deltaChunkX, centerChunkZ + deltaChunkZ)) continue; - renderedPos.add(new DhChunkPos(centerChunkX + deltaChunkX, centerChunkZ + deltaChunkZ)); - } - } - return renderedPos; - } - /** Can return null if the given level hasn't had a light map assigned to it */ @Nullable ILightMapWrapper getLightmapWrapper(ILevelWrapper level); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java index 82ca5efc0..c7eda6885 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java @@ -25,8 +25,6 @@ import java.util.HashSet; public interface ISodiumAccessor extends IModAccessor { - HashSet getNormalRenderedChunks(); - /** A temporary overwrite for a config in sodium 0.5 to fix their terrain from showing, will be removed once a proper fix is added */ void setFogOcclusion(boolean b); // FIXME From c658269eb71b82a0becb2868e84b9540b651c81c Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 30 Jul 2024 17:06:55 -0500 Subject: [PATCH 09/27] Remove deprecated methods and move method to StringUtil --- .../coreapi/util/StringUtil.java | 24 +++- .../structure/ClientOnlySaveStructure.java | 4 +- .../SubDimensionLevelMatcher.java | 13 ++- .../distanthorizons/core/util/LodUtil.java | 109 +----------------- .../distanthorizons/core/util/RenderUtil.java | 100 ---------------- 5 files changed, 32 insertions(+), 218 deletions(-) diff --git a/api/src/main/java/com/seibel/distanthorizons/coreapi/util/StringUtil.java b/api/src/main/java/com/seibel/distanthorizons/coreapi/util/StringUtil.java index f57efbe15..44c94b197 100644 --- a/api/src/main/java/com/seibel/distanthorizons/coreapi/util/StringUtil.java +++ b/api/src/main/java/com/seibel/distanthorizons/coreapi/util/StringUtil.java @@ -24,12 +24,12 @@ import java.util.Arrays; /** * Miscellaneous string helper functions. - * - * @author James Seibel - * @version 2022-7-19 */ public class StringUtil { + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + + /** * Returns the n-th index of the given string.

* @@ -67,8 +67,6 @@ public class StringUtil return stringBuilder.toString(); } - - private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); /** * Converts the given byte array into a hex string representation.
* source: https://stackoverflow.com/a/9855338 @@ -85,4 +83,20 @@ public class StringUtil return new String(hexChars); } + /** + * Returns a shortened version of the given string that is no longer than maxLength.
+ * If null returns the empty string. + */ + public static String shortenString(String str, int maxLength) + { + if (str == null) + { + return ""; + } + else + { + return str.substring(0, Math.min(str.length(), maxLength)); + } + } + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java index 17a2c4d90..deed8d479 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java @@ -26,12 +26,12 @@ import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.api.enums.config.EDhApiServerFolderNameMode; import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel; import com.seibel.distanthorizons.core.util.objects.ParsedIp; -import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IDimensionTypeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import com.seibel.distanthorizons.coreapi.util.StringUtil; import java.io.File; import java.util.*; @@ -138,7 +138,7 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure { // use the first existing sub-dimension String folderName = folders.get(0).getName(); - LOGGER.info("Default Sub Dimension set to: [" + LodUtil.shortenString(folderName, 8) + "...]"); + LOGGER.info("Default Sub Dimension set to: [" + StringUtil.shortenString(folderName, 8) + "...]"); return folders.get(0); } else diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java b/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java index 89fe2a3f4..ac8225962 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java @@ -40,6 +40,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import com.seibel.distanthorizons.coreapi.util.StringUtil; import it.unimi.dsi.fastutil.longs.LongArrayList; import org.apache.logging.log4j.LogManager; @@ -92,7 +93,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable if (potentialLevelFolders.size() == 0) { String newId = UUID.randomUUID().toString(); - LOGGER.info("No potential level files found. Creating a new sub dimension with the ID ["+LodUtil.shortenString(newId, 8)+"]..."); + LOGGER.info("No potential level files found. Creating a new sub dimension with the ID ["+ StringUtil.shortenString(newId, 8)+"]..."); this.foundLevelFile = this.CreateSubDimFolder(newId); } } @@ -207,7 +208,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable SubDimCompare mostSimilarSubDim = null; for (File testLevelFolder : this.potentialLevelFolders) { - LOGGER.info("Testing level folder: [" + LodUtil.shortenString(testLevelFolder.getName(), 8) + "]"); + LOGGER.info("Testing level folder: [" + StringUtil.shortenString(testLevelFolder.getName(), 8) + "]"); FullDataSourceV2 testFullDataSource = null; try @@ -328,8 +329,8 @@ public class SubDimensionLevelMatcher implements AutoCloseable } - String subDimShortName = LodUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging - String equalPercent = LodUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5); + String subDimShortName = StringUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging + String equalPercent = StringUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5); LOGGER.info("Sub dimension ["+subDimShortName+"...] is current dimension probability: "+equalPercent+" ("+equalDataPoints+"/"+totalDataPointCount+")"); } catch (Exception e) @@ -359,7 +360,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable { // we found a sub dim folder that is similar, use it - LOGGER.info("Sub Dimension set to: [" + LodUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]"); + LOGGER.info("Sub Dimension set to: [" + StringUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]"); return mostSimilarSubDim.folder; } else @@ -369,7 +370,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable String newId = UUID.randomUUID().toString(); double highestEqualityPercent = mostSimilarSubDim != null ? mostSimilarSubDim.getPercentEqual() : 0; - String message = "No suitable sub dimension found. The highest equality was [" + LodUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + LodUtil.shortenString(newId, 8) + "..."; + String message = "No suitable sub dimension found. The highest equality was [" + StringUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + StringUtil.shortenString(newId, 8) + "..."; LOGGER.info(message); File folder = this.CreateSubDimFolder(newId); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/LodUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/LodUtil.java index 8c652ebda..826ac5975 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/LodUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/LodUtil.java @@ -20,7 +20,6 @@ package com.seibel.distanthorizons.core.util; import java.util.Arrays; -import java.util.Iterator; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletionException; @@ -41,9 +40,6 @@ import org.apache.logging.log4j.Logger; /** * This class holds methods and constants that may be used in multiple places. - * - * @author James Seibel - * @version 2022-12-5 */ public class LodUtil { @@ -51,31 +47,7 @@ public class LodUtil private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - /** - * Vanilla render distances less than or equal to this will not allow partial - * overdraw. The VanillaOverdraw will either be ALWAYS or NEVER. - */ - public static final int MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW = 4; - /** - * Vanilla render distances less than or equal to this will cause the overdraw to - * run at a smaller fraction of the vanilla render distance. - */ - public static final int MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW = 11; - - - - - /** - * alpha used when drawing chunks in debug mode - */ - public static final int DEBUG_ALPHA = 255; // 0 - 25; - - public static final int COLOR_DEBUG_BLACK = ColorUtil.rgbToInt(DEBUG_ALPHA, 0, 0, 0); - public static final int COLOR_DEBUG_WHITE = ColorUtil.rgbToInt(DEBUG_ALPHA, 255, 255, 255); - public static final int COLOR_INVISIBLE = ColorUtil.rgbToInt(0, 0, 0, 0); - - //FIXME: WE NEED MORE COLORS!!!! /** * In order of nearest to farthest:
* Red, Orange, Yellow, Green, Cyan, Blue, Magenta, white, gray, black @@ -163,62 +135,9 @@ public class LodUtil - - /** - * Gets the ServerWorld for the relevant dimension. - * - * @return null if there is no ServerWorld for the given dimension - */ - public static ILevelWrapper getServerWorldFromDimension(IDimensionTypeWrapper newDimension) - { - if (!MC_CLIENT.hasSinglePlayerServer()) - return null; - - Iterable worlds = MC_CLIENT.getAllServerWorlds(); - ILevelWrapper returnWorld = null; - - for (ILevelWrapper world : worlds) - { - if (world.getDimensionType() == newDimension) - { - returnWorld = world; - break; - } - } - - return returnWorld; - } - - - public static int computeOverdrawOffset() - { - int chunkRenderDist = MC_RENDER.getRenderDistance() + 1; - EDhApiVanillaOverdraw overdraw = EDhApiVanillaOverdraw.ALWAYS; //Config.Client.Advanced.Graphics.AdvancedGraphics.vanillaOverdraw.get(); - if (overdraw == EDhApiVanillaOverdraw.ALWAYS) return Integer.MAX_VALUE; - - int offset; - if (overdraw == EDhApiVanillaOverdraw.NEVER) - { - offset = 0; //Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawOffset.get(); - } - else - { - if (chunkRenderDist < MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW) - { - offset = 1; - } - else - { - offset = chunkRenderDist / 5; - } - } - - if (chunkRenderDist - offset <= 1) - { - return Integer.MAX_VALUE; - } - return offset; - } + //=========// + // methods // + //=========// /** Returns the chunk int position for the given double position */ public static int getChunkPosFromDouble(double value) { return (int) Math.floor(value / CHUNK_WIDTH); } @@ -252,27 +171,7 @@ public class LodUtil * Do not use it for deserialization or naming of objects. * @author leetom */ - public static String formatLog(String str, Object... param) - { - return LOGGER.getMessageFactory().newMessage(str, param).getFormattedMessage(); - } - - // TODO move - /** - * Returns a shortened version of the given string that is no longer than maxLength.
- * If null returns the empty string. - */ - public static String shortenString(String str, int maxLength) - { - if (str == null) - { - return ""; - } - else - { - return str.substring(0, Math.min(str.length(), maxLength)); - } - } + public static String formatLog(String str, Object... param) { return LOGGER.getMessageFactory().newMessage(str, param).getFormattedMessage(); } public static class AssertFailureException extends RuntimeException { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java index b4967d0e5..e27a1afae 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java @@ -48,93 +48,6 @@ public class RenderUtil - //=================// - // culling methods // - //=================// - - /** - * Returns if the given ChunkPos is in the loaded area of the world. - * - * @param center the center of the loaded world (probably the player's ChunkPos) - */ - public static boolean isChunkPosInLoadedArea(DhChunkPos pos, DhChunkPos center) - { - return (pos.x >= center.x - MC_RENDER.getRenderDistance() - && pos.x <= center.x + MC_RENDER.getRenderDistance()) - && - (pos.z >= center.z - MC_RENDER.getRenderDistance() - && pos.z <= center.z + MC_RENDER.getRenderDistance()); - } - - /** - * Returns if the given coordinate is in the loaded area of the world. - * - * @param centerCoordinate the center of the loaded world - */ - public static boolean isCoordinateInLoadedArea(int x, int z, int centerCoordinate) - { - return (x >= centerCoordinate - MC_RENDER.getRenderDistance() - && x <= centerCoordinate + MC_RENDER.getRenderDistance()) - && - (z >= centerCoordinate - MC_RENDER.getRenderDistance() - && z <= centerCoordinate + MC_RENDER.getRenderDistance()); - } - - /** - * Find the coordinates that are in the center half of the given - * 2D matrix, starting at (0,0) and going to (2 * lodRadius, 2 * lodRadius). - */ - public static boolean isCoordinateInNearFogArea(int i, int j, int lodRadius) - { - int halfRadius = lodRadius / 2; - - return (i >= lodRadius - halfRadius - && i <= lodRadius + halfRadius) - && - (j >= lodRadius - halfRadius - && j <= lodRadius + halfRadius); - } - - /** - * Returns true if one of the region's 4 corners is in front - * of the camera. - */ - public static boolean isRegionInViewFrustum(DhBlockPos playerBlockPos, Vec3f cameraDir, int vboRegionX, int vboRegionZ) - { - // convert the vbo position into a direction vector - // starting from the player's position - Vec3f vboVec = new Vec3f(vboRegionX * LodUtil.REGION_WIDTH, 0, vboRegionZ * LodUtil.REGION_WIDTH); - Vec3f playerVec = new Vec3f(playerBlockPos.x, playerBlockPos.y, playerBlockPos.z); - - vboVec.subtract(playerVec); - - // calculate the 4 corners - Vec3f vboSeVec = new Vec3f(vboVec.x + LodUtil.REGION_WIDTH, vboVec.y, vboVec.z + LodUtil.REGION_WIDTH); - Vec3f vboSwVec = new Vec3f(vboVec.x, vboVec.y, vboVec.z + LodUtil.REGION_WIDTH); - Vec3f vboNwVec = new Vec3f(vboVec.x, vboVec.y, vboVec.z); - Vec3f vboNeVec = new Vec3f(vboVec.x + LodUtil.REGION_WIDTH, vboVec.y, vboVec.z); - - // if any corner is visible, this region should be rendered - return isNormalizedVectorInViewFrustum(vboSeVec, cameraDir) || - isNormalizedVectorInViewFrustum(vboSwVec, cameraDir) || - isNormalizedVectorInViewFrustum(vboNwVec, cameraDir) || - isNormalizedVectorInViewFrustum(vboNeVec, cameraDir); - } - - /** - * Currently takes the dot product of the two vectors, - * but in the future could do more complicated frustum culling tests. - */ - private static boolean isNormalizedVectorInViewFrustum(Vec3f objectVector, Vec3f cameraDir) - { - // the -0.1 is to offer a slight buffer, so we are - // more likely to render LODs and thus, hopefully prevent - // flickering or odd disappearances - return objectVector.dotProduct(cameraDir) > -0.1; - } - - - //=====================// // matrix manipulation // //=====================// @@ -166,19 +79,6 @@ public class RenderUtil return mcModelViewMat.copy(); } - /** - * create and return a new combined modelView/projection matrix based on MC's modelView and projection matrices - * - * @param mcProjMat Minecraft's current projection matrix - * @param mcModelViewMat Minecraft's current model view matrix - */ - public static Mat4f createCombinedModelViewProjectionMatrix(Mat4f mcProjMat, Mat4f mcModelViewMat, float partialTicks) - { - Mat4f lodProj = createLodProjectionMatrix(mcProjMat, partialTicks); - lodProj.multiply(createLodModelViewMatrix(mcModelViewMat)); - return lodProj; - } - public static float getNearClipPlaneDistanceInBlocks(float partialTicks) { int chunkRenderDistance = MC_RENDER.getRenderDistance(); From 0c36dc03a55488c259bdbadf239dec50396004ac Mon Sep 17 00:00:00 2001 From: James Seibel Date: Wed, 31 Jul 2024 19:06:44 -0500 Subject: [PATCH 10/27] Fix cave culling affecting floating islands and add LOD reload to some configs --- .../distanthorizons/core/config/Config.java | 32 ++++++++++++------- ...java => ReloadLodsConfigEventHandler.java} | 4 +-- .../FullDataToRenderDataTransformer.java | 6 +++- 3 files changed, 27 insertions(+), 15 deletions(-) rename core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/{DebugColumnConfigEventHandler.java => ReloadLodsConfigEventHandler.java} (88%) 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 a24289202..828d1e5a0 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 @@ -54,7 +54,6 @@ import java.util.List; * Otherwise, you will have issues where only some of the config entries will exist when your listener is created. * * @author coolGi - * @version 2023-7-16 */ public class Config @@ -179,6 +178,7 @@ public class Config + "Lowest Quality: " + EDhApiVerticalQuality.HEIGHT_MAP + "\n" + "Highest Quality: " + EDhApiVerticalQuality.EXTREME) .setPerformance(EConfigEntryPerformance.VERY_HIGH) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry horizontalQuality = new ConfigEntry.Builder() @@ -200,6 +200,7 @@ public class Config + EDhApiTransparency.DISABLED + ": LODs will be opaque. \n" + "") .setPerformance(EConfigEntryPerformance.MEDIUM) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry blocksToIgnore = new ConfigEntry.Builder() @@ -211,6 +212,7 @@ public class Config + EDhApiBlocksToAvoid.NON_COLLIDING + ": Only represent solid blocks in the LODs (tall grass, torches, etc. won't count for a LOD's height) \n" + "") .setPerformance(EConfigEntryPerformance.NONE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry tintWithAvoidedBlocks = new ConfigEntry.Builder() @@ -222,6 +224,7 @@ public class Config + "False: skipped blocks will not change color of surface below them. " + "") .setPerformance(EConfigEntryPerformance.NONE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); // TODO fixme @@ -587,6 +590,7 @@ public class Config + "0 = black \n" + "1 = normal \n" + "2 = near white") + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry saturationMultiplier = new ConfigEntry.Builder() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats) @@ -597,6 +601,7 @@ public class Config + "0 = black and white \n" + "1 = normal \n" + "2 = very saturated") + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry enableCaveCulling = new ConfigEntry.Builder() @@ -611,14 +616,15 @@ public class Config + "Additional Info: Currently this cull all faces \n" + " with skylight value of 0 in dimensions that \n" + " does not have a ceiling.") + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); - @Deprecated public static ConfigEntry caveCullingHeight = new ConfigEntry.Builder() - .setMinDefaultMax(-4096, 40, 4096) - .setAppearance(EConfigEntryAppearance.ONLY_IN_API) + .setMinDefaultMax(-4096, 60, 4096) .comment("" - + "At what Y value should cave culling start?") + + "At what Y value should cave culling start? \n" + + "Lower this value if you get walls for areas with 0 light.") + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry earthCurveRatio = new ConfigEntry.Builder() @@ -656,6 +662,7 @@ public class Config + EDhApiLodShading.DISABLED + ": All LOD sides will be rendered with the same brightness. \n" + "") .setPerformance(EConfigEntryPerformance.NONE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry disableFrustumCulling = new ConfigEntry.Builder() @@ -690,6 +697,7 @@ public class Config + EDhApiGrassSideRendering.AS_DIRT + ": sides render entirely as dirt. \n" + "") .setPerformance(EConfigEntryPerformance.NONE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); } @@ -1303,38 +1311,38 @@ public class Config public static ConfigEntry columnBuilderDebugEnable = new ConfigEntry.Builder() .set(false) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry columnBuilderDebugDetailLevel = new ConfigEntry.Builder() .set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry columnBuilderDebugXPos = new ConfigEntry.Builder() .set(0) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry columnBuilderDebugZPos = new ConfigEntry.Builder() .set(0) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry columnBuilderDebugXRow = new ConfigEntry.Builder() .set(-1) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry columnBuilderDebugZRow = new ConfigEntry.Builder() .set(-1) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry columnBuilderDebugColumnIndex = new ConfigEntry.Builder() .set(-1) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/DebugColumnConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/ReloadLodsConfigEventHandler.java similarity index 88% rename from core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/DebugColumnConfigEventHandler.java rename to core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/ReloadLodsConfigEventHandler.java index 93f55820d..9248280e5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/DebugColumnConfigEventHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/ReloadLodsConfigEventHandler.java @@ -23,9 +23,9 @@ import com.seibel.distanthorizons.api.DhApi; import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy; import com.seibel.distanthorizons.core.config.listeners.IConfigListener; -public class DebugColumnConfigEventHandler implements IConfigListener +public class ReloadLodsConfigEventHandler implements IConfigListener { - public static DebugColumnConfigEventHandler INSTANCE = new DebugColumnConfigEventHandler(); + public static ReloadLodsConfigEventHandler INSTANCE = new ReloadLodsConfigEventHandler(); @Override public void onConfigValueSet() diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java index 2ac1d4a0b..3cf9194e5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java @@ -178,6 +178,7 @@ public class FullDataToRenderDataTransformer HashSet blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(level.getLevelWrapper()); HashSet caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(level.getLevelWrapper()); + int caveCullingMaxY = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get() - level.getMinY(); boolean caveCullingEnabled = Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get() && ( @@ -211,6 +212,7 @@ public class FullDataToRenderDataTransformer int bottomY = FullDataPointUtil.getBottomY(fullData); int blockHeight = FullDataPointUtil.getHeight(fullData); + int topY = bottomY + blockHeight; int id = FullDataPointUtil.getId(fullData); int blockLight = FullDataPointUtil.getBlockLight(fullData); int skyLight = FullDataPointUtil.getSkyLight(fullData); @@ -252,7 +254,9 @@ public class FullDataToRenderDataTransformer { if (caveCullingEnabled // assume this data point is underground if it has no sky-light - && skyLight == LodUtil.MIN_MC_LIGHT + && skyLight == LodUtil.MIN_MC_LIGHT + // ignore caves above a certain height to prevent floating islands from having walls underneath them + && topY < caveCullingMaxY // cave culling shouldn't happen when at the top of the world && columnOffset != 0 // cave culling can't happen when at the bottom of the world From 116d7f4a3f78cb692e75f4b78bc10b27f8890feb Mon Sep 17 00:00:00 2001 From: James Seibel Date: Thu, 1 Aug 2024 06:52:05 -0500 Subject: [PATCH 11/27] Fix incorrect comment in DhSectionPos --- .../com/seibel/distanthorizons/core/pos/DhSectionPos.java | 4 ++-- 1 file changed, 2 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 e55072dbd..b7ac06d40 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 @@ -34,11 +34,11 @@ import java.util.function.LongConsumer; * 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}).

* - * Why does the smallest render section represent 2x2 MC chunks (section detail level 6)?
+ * Why does the smallest render section represent 4x4 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). + * With those thoughts in mind we decided on a smallest section size of 64 data points square (IE 4x4 chunks). * * @author Leetom */ From b7fea649251b2d7d27dfb0b4fc89c645513072b1 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Fri, 2 Aug 2024 17:56:25 -0500 Subject: [PATCH 12/27] Fix out of bounds exception in Full Data Transformer --- .../FullDataToRenderDataTransformer.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java index 3cf9194e5..3228280af 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java @@ -197,7 +197,7 @@ public class FullDataToRenderDataTransformer int skylightToApplyToNextBlock = -1; int blocklightToApplyToNextBlock = -1; - int columnOffset = 0; + int renderDataIndex = 0; @@ -206,9 +206,9 @@ public class FullDataToRenderDataTransformer //==================================// // goes from the top down - for (int i = 0; i < fullColumnData.size(); i++) + for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++) { - long fullData = fullColumnData.getLong(i); + long fullData = fullColumnData.getLong(fullDataIndex); int bottomY = FullDataPointUtil.getBottomY(fullData); int blockHeight = FullDataPointUtil.getHeight(fullData); @@ -258,22 +258,22 @@ public class FullDataToRenderDataTransformer // ignore caves above a certain height to prevent floating islands from having walls underneath them && topY < caveCullingMaxY // cave culling shouldn't happen when at the top of the world - && columnOffset != 0 + && renderDataIndex != 0 && fullDataIndex != 0 // cave culling can't happen when at the bottom of the world - && columnOffset != fullColumnData.size()) + && fullDataIndex != fullColumnData.size()) { // we need to get the next sky/block lights because // the air block here will always have a light of 0/0 due to only the top of the LOD's light being saved. - long nextFullData = fullColumnData.getLong(i+1); + long nextFullData = fullColumnData.getLong(fullDataIndex+1); int nextSkyLight = FullDataPointUtil.getSkyLight(nextFullData); if (nextSkyLight == LodUtil.MIN_MC_LIGHT && ColorUtil.getAlpha(lastColor) == 255) { // replace the previous block with new bottom - long columnData = renderColumnData.get(columnOffset - 1); + long columnData = renderColumnData.get(renderDataIndex - 1); columnData = RenderDataPointUtil.setYMin(columnData, bottomY); - renderColumnData.set(columnOffset - 1, columnData); + renderColumnData.set(renderDataIndex - 1, columnData); } continue; @@ -341,20 +341,20 @@ public class FullDataToRenderDataTransformer //=============================// // check if they share a top-bottom face and if they have same color - if (color == lastColor && bottomY + blockHeight == lastBottom && columnOffset > 0) + if (color == lastColor && bottomY + blockHeight == lastBottom && renderDataIndex > 0) { //replace the previous block with new bottom - long columnData = renderColumnData.get(columnOffset - 1); + long columnData = renderColumnData.get(renderDataIndex - 1); columnData = RenderDataPointUtil.setYMin(columnData, bottomY); - renderColumnData.set(columnOffset - 1, columnData); + renderColumnData.set(renderDataIndex - 1, columnData); } else { // add the block isColumnVoid = false; long columnData = RenderDataPointUtil.createDataPoint(bottomY + blockHeight, bottomY, color, skyLight, blockLight, block.getMaterialId()); - renderColumnData.set(columnOffset, columnData); - columnOffset++; + renderColumnData.set(renderDataIndex, columnData); + renderDataIndex++; } lastBottom = bottomY; lastColor = color; From c43b985f4ec2309130e5ae99da6d945057a01e7c Mon Sep 17 00:00:00 2001 From: James Seibel Date: Fri, 2 Aug 2024 18:30:55 -0500 Subject: [PATCH 13/27] Fix off by 1 error in Render data transformer --- .../transformers/FullDataToRenderDataTransformer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java index 3228280af..61f639e78 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java @@ -260,7 +260,7 @@ public class FullDataToRenderDataTransformer // cave culling shouldn't happen when at the top of the world && renderDataIndex != 0 && fullDataIndex != 0 // cave culling can't happen when at the bottom of the world - && fullDataIndex != fullColumnData.size()) + && (fullDataIndex+1) < fullColumnData.size()) { // we need to get the next sky/block lights because // the air block here will always have a light of 0/0 due to only the top of the LOD's light being saved. From 1ecf9686683246be09c41bdb54098d3a37ca7fab Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 3 Aug 2024 09:32:29 -0500 Subject: [PATCH 14/27] Remove unused ChunkToLodBuilder --- .../transformers/ChunkToLodBuilder.java | 253 ------------------ .../core/level/AbstractDhLevel.java | 18 +- .../core/level/DhClientLevel.java | 1 - .../core/level/DhClientServerLevel.java | 2 +- .../core/level/DhServerLevel.java | 2 +- 5 files changed, 3 insertions(+), 273 deletions(-) delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java deleted file mode 100644 index 7336b42bd..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * This file is part of the Distant Horizons mod - * licensed under the GNU LGPL v3 License. - * - * Copyright (C) 2020-2023 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.seibel.distanthorizons.core.dataObjects.transformers; - -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; - -import com.seibel.distanthorizons.core.config.Config; -import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; -import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; -import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; -import com.seibel.distanthorizons.core.pos.DhChunkPos; -import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; -import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; -import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; -import org.apache.logging.log4j.LogManager; - -public class ChunkToLodBuilder implements AutoCloseable -{ - public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), () -> Config.Client.Advanced.Logging.logLodBuilderEvent.get()); - private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - - public static final long MAX_TICK_TIME_NS = 1000000000L / 20L; - - private final ConcurrentHashMap concurrentChunkToBuildByChunkPos = new ConcurrentHashMap<>(); - private final ConcurrentLinkedDeque concurrentTaskToBuildList = new ConcurrentLinkedDeque<>(); - private final AtomicInteger runningCount = new AtomicInteger(0); - - - - //==============// - // constructors // - //==============// - - public ChunkToLodBuilder() { } - - - - //=================// - // data generation // - //=================// - - public CompletableFuture tryGenerateData(IChunkWrapper chunkWrapper) - { - if (chunkWrapper == null) - { - throw new NullPointerException("ChunkWrapper cannot be null!"); - } - - IChunkWrapper oldChunk = this.concurrentChunkToBuildByChunkPos.put(chunkWrapper.getChunkPos(), chunkWrapper); // an Exchange operation - // If there's old chunk, that means we just replaced an unprocessed old request on generating data on this pos. - // if so, we can just return null to signal this, as the old request's future will instead be the proper one - // that will return the latest generated data. - if (oldChunk != null) - { - return null; - } - - // Otherwise, it means we're the first to do so. Let's submit our task to this entry. - CompletableFuture future = new CompletableFuture<>(); - this.concurrentTaskToBuildList.addLast(new Task(chunkWrapper.getChunkPos(), future)); - return future; - } - - // TODO why on tick? - public void tick() - { - int threadCount = ThreadPoolUtil.getWorkerThreadCount(); - if (this.runningCount.get() >= threadCount) - { - return; - } - else if (this.concurrentTaskToBuildList.isEmpty()) - { - return; - } - else if (MC == null || !MC.playerExists()) - { - // TODO handle server side properly - - // MC hasn't finished loading (or is currently unloaded) - - // can be uncommented if tasks aren't being cleared correctly - //this.clearCurrentTasks(); - return; - } - - ThreadPoolExecutor lodBuilderExecutor = ThreadPoolUtil.getChunkToLodBuilderExecutor(); - if (lodBuilderExecutor == null) - { - return; - } - - - for (int i = 0; i < threadCount; i++) - { - this.runningCount.incrementAndGet(); - try - { - CompletableFuture.runAsync(() -> - { - try - { - this.tickThreadTask(); - } - finally - { - this.runningCount.decrementAndGet(); - } - }, lodBuilderExecutor); - } - catch (RejectedExecutionException ignore) { /* the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back */ } - } - } - private void tickThreadTask() - { - long time = System.nanoTime(); - int count = 0; - boolean allDone = false; - while (true) - { - // run until we either run out of time, or all tasks are complete - if (System.nanoTime() - time > MAX_TICK_TIME_NS && !this.concurrentTaskToBuildList.isEmpty()) - { - break; - } - - Task task = this.concurrentTaskToBuildList.pollFirst(); - if (task == null) - { - allDone = true; - break; - } - - count++; - IChunkWrapper latestChunk = this.concurrentChunkToBuildByChunkPos.remove(task.chunkPos); // Basically an Exchange operation - if (latestChunk == null) - { - LOGGER.error("Somehow Task at " + task.chunkPos + " has latestChunk as null. Skipping task."); - task.future.complete(null); - continue; - } - - try - { - if (LodDataBuilder.canGenerateLodFromChunk(latestChunk)) - { - FullDataSourceV2 dataSource = LodDataBuilder.createGeneratedDataSource(latestChunk); - if (dataSource != null) - { - task.future.complete(dataSource); - continue; - } - } - else if (task.generationAttemptExpirationTimeMs < System.currentTimeMillis()) - { - // this task won't be re-queued - //LOGGER.trace("removed chunk "+task.chunkPos); - continue; - } - } - catch (Exception ex) - { - LOGGER.error("Error while processing Task at " + task.chunkPos, ex); - } - - // Failed to build due to chunk not meeting requirement, - // re-add it to the queue so it can be tested next time - IChunkWrapper casChunk = this.concurrentChunkToBuildByChunkPos.putIfAbsent(task.chunkPos, latestChunk); // CAS operation with expected=null - if (casChunk == null || latestChunk.isStillValid()) // That means CAS have been successful - { - this.concurrentTaskToBuildList.addLast(task); // Then add back the same old task. - } - else // Else, it means someone managed to sneak in a new gen request in this pos. Then lets drop this old task. - { - task.future.complete(null); - } - - count--; - } - - long time2 = System.nanoTime(); - if (!allDone) - { - //LOGGER.info("Completed {} tasks in {} in this tick", count, Duration.ofNanos(time2 - time)); - } - else if (count > 0) - { - //LOGGER.info("Completed all {} tasks in {}", count, Duration.ofNanos(time2 - time)); - } - } - - /** - * should be called whenever changing levels/worlds - * to prevent trying to generate LODs for chunk(s) that are no longer loaded - * (which can cause exceptions) - */ - public void clearCurrentTasks() - { - this.concurrentTaskToBuildList.clear(); - this.concurrentChunkToBuildByChunkPos.clear(); - } - - - - - //==============// - // base methods // - //==============// - - @Override - public void close() { this.clearCurrentTasks(); } - - - - //================// - // helper classes // - //================// - - private static class Task - { - public final DhChunkPos chunkPos; - public final CompletableFuture future; - /** This is tracked so impossible tasks can be removed from the queue */ - public long generationAttemptExpirationTimeMs = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10); - - Task(DhChunkPos chunkPos, CompletableFuture future) - { - this.chunkPos = chunkPos; - this.future = future; - } - - } - -} - diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java index 16f7b6694..523a5a376 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java @@ -19,28 +19,19 @@ package com.seibel.distanthorizons.core.level; -import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup; import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkModifiedEvent; -import com.seibel.distanthorizons.api.objects.math.DhApiVec3f; -import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox; -import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading; -import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; -import com.seibel.distanthorizons.core.dataObjects.transformers.ChunkToLodBuilder; import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; -import com.seibel.distanthorizons.core.pos.DhBlockPos; import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler; import com.seibel.distanthorizons.core.render.renderer.generic.CloudRenderHandler; import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer; -import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO; import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo; import com.seibel.distanthorizons.core.sql.repo.ChunkHashRepo; -import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import org.apache.logging.log4j.Logger; @@ -59,8 +50,6 @@ public abstract class AbstractDhLevel implements IDhLevel { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - public final ChunkToLodBuilder chunkToLodBuilder; - /** if this is null then the other handler is probably null too, but just in case */ @Nullable public ChunkHashRepo chunkHashRepo; @@ -83,10 +72,7 @@ public abstract class AbstractDhLevel implements IDhLevel // constructor // //=============// - protected AbstractDhLevel() - { - this.chunkToLodBuilder = new ChunkToLodBuilder(); - } + protected AbstractDhLevel() { } /** * Creating the repos requires access to the level file, which isn't @@ -262,8 +248,6 @@ public abstract class AbstractDhLevel implements IDhLevel @Override public void close() { - this.chunkToLodBuilder.close(); - if (this.chunkHashRepo != null) { this.chunkHashRepo.close(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java index 0b5555b30..143327d5d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java @@ -86,7 +86,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel { try { - this.chunkToLodBuilder.tick(); this.clientside.clientTick(); } catch (Exception e) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java index b9357e6ff..9427dce50 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java @@ -95,7 +95,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev { this.clientside.renderDeferred(renderEventParam, profiler); } @Override - public void serverTick() { this.chunkToLodBuilder.tick(); } + public void serverTick() { } @Override public void doWorldGen() diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index 238b50e46..355dae28c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -65,7 +65,7 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel // methods // //=========// - public void serverTick() { this.chunkToLodBuilder.tick(); } + public void serverTick() { } @Override public CompletableFuture updateDataSourcesAsync(FullDataSourceV2 data) { return this.getFullDataProvider().updateDataSourceAsync(data); } From 377d0fbe1201e0db6f2920ba0a316aae4e4679fb Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 3 Aug 2024 09:32:57 -0500 Subject: [PATCH 15/27] Allow DhApiChunk to accept top down or bottom up data point orders --- .../api/objects/data/DhApiChunk.java | 53 ++++++++++++++----- .../transformers/LodDataBuilder.java | 2 + 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java index bf74fabfd..fd8a064c9 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java @@ -21,8 +21,10 @@ package com.seibel.distanthorizons.api.objects.data; import com.seibel.distanthorizons.api.interfaces.factories.IDhApiWrapperFactory; import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator; +import org.apache.logging.log4j.LogManager; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -114,27 +116,50 @@ public class DhApiChunk */ public void setDataPoints(int relX, int relZ, List dataPoints) throws IndexOutOfBoundsException, IllegalArgumentException { + //================// + // validate input // + //================// + + int internalArrayIndex = (relZ << 4) | relX; throwIfRelativePosOutOfBounds(relX, relZ); - // validate the incoming datapoints - if (dataPoints != null) + // ignore empty inputs + if (dataPoints == null) { - for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation + return; + } + + // check that each datapoint is valid + for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation + { + DhApiTerrainDataPoint dataPoint = dataPoints.get(i); + if (dataPoint == null) { - DhApiTerrainDataPoint dataPoint = dataPoints.get(i); - if (dataPoint == null) - { - throw new IllegalArgumentException("Null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR."); - } - - if (dataPoint.detailLevel != 0) - { - throw new IllegalArgumentException("DhApiTerrainDataPoints has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0]."); - } + throw new IllegalArgumentException("Null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR."); + } + + if (dataPoint.detailLevel != 0) + { + throw new IllegalArgumentException("DhApiTerrainDataPoints has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0]."); } } - this.dataPoints.set((relZ << 4) | relX, dataPoints); + + + //================// + // set datapoints // + //================// + + // DH expects datapoints to be in a top-down order + DhApiTerrainDataPoint first = dataPoints.get(0); + DhApiTerrainDataPoint last = dataPoints.get(dataPoints.size() - 1); + if (first.bottomYBlockPos < last.bottomYBlockPos) + { + // flip the array if it's in bottom-up order + Collections.reverse(dataPoints); + } + + this.dataPoints.set(internalArrayIndex, dataPoints); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java index 6b4d1861e..0cd1b2f12 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java @@ -320,6 +320,8 @@ public class LodDataBuilder // AND the below loop won't run. int size = (columnDataPoints != null) ? columnDataPoints.size() : 0; + // TODO make missing air LODs + // TODO merge duplicate datapoints LongArrayList packedDataPoints = new LongArrayList(new long[size]); for (int index = 0; index < size; index++) { From 801a126de0fb67d85cdf1e36493eba120118d997 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 3 Aug 2024 16:50:12 -0500 Subject: [PATCH 16/27] Fix chunk has being saved before LOD update --- .../core/api/internal/SharedApi.java | 3 +-- .../core/level/AbstractDhLevel.java | 19 ++++++++++--------- .../distanthorizons/core/level/IDhLevel.java | 3 +-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index 11fda5913..2fb2d6874 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -356,8 +356,7 @@ public class SharedApi dhLevel.setBeaconBeamsForChunk(chunkWrapper.getChunkPos(), beaconBeamList); - dhLevel.updateChunkAsync(chunkWrapper); - dhLevel.setChunkHash(chunkWrapper.getChunkPos(), newChunkHash); + dhLevel.updateChunkAsync(chunkWrapper, newChunkHash); } catch (Exception e) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java index 523a5a376..c255b5e90 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java @@ -60,6 +60,7 @@ public abstract class AbstractDhLevel implements IDhLevel protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 2_000); /** contains the {@link DhChunkPos} for each {@link DhSectionPos} that are queued to save via {@link AbstractDhLevel#delayedFullDataSourceSaveCache} */ protected final ConcurrentHashMap> updatedChunkPosSetBySectionPos = new ConcurrentHashMap<>(); + protected final ConcurrentHashMap updatedChunkHashesByChunkPos = new ConcurrentHashMap<>(); /** Will be null if clouds shouldn't be rendered for this level. */ @Nullable @@ -138,7 +139,7 @@ public abstract class AbstractDhLevel implements IDhLevel public int getUnsavedDataSourceCount() { return this.delayedFullDataSourceSaveCache.getUnsavedCount(); } @Override - public void updateChunkAsync(IChunkWrapper chunkWrapper) + public void updateChunkAsync(IChunkWrapper chunkWrapper, int chunkHash) { FullDataSourceV2 dataSource = FullDataSourceV2.createFromChunk(chunkWrapper); if (dataSource == null) @@ -157,6 +158,7 @@ public abstract class AbstractDhLevel implements IDhLevel chunkPosSet.add(chunkWrapper.getChunkPos()); return chunkPosSet; }); + this.updatedChunkHashesByChunkPos.put(chunkWrapper.getChunkPos(), chunkHash); // batch updates to reduce overhead when flying around or breaking/placing a lot of blocks in an area this.delayedFullDataSourceSaveCache.queueDataSourceForUpdateAndSave(dataSource); @@ -171,6 +173,13 @@ public abstract class AbstractDhLevel implements IDhLevel { for (DhChunkPos chunkPos : updatedChunkPosSet) { + // save after the data source has been updated to prevent saving the hash without the associated datasource + Integer chunkHash = this.updatedChunkHashesByChunkPos.remove(chunkPos); + if (this.chunkHashRepo != null && chunkHash != null) + { + this.chunkHashRepo.save(new ChunkHashDTO(chunkPos, chunkHash)); + } + ApiEventInjector.INSTANCE.fireAllEvents( DhApiChunkModifiedEvent.class, new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.x, chunkPos.z)); @@ -198,14 +207,6 @@ public abstract class AbstractDhLevel implements IDhLevel ChunkHashDTO dto = this.chunkHashRepo.getByKey(pos); return (dto != null) ? dto.chunkHash : 0; } - @Override - public void setChunkHash(DhChunkPos pos, int chunkHash) - { - if (this.chunkHashRepo != null) - { - this.chunkHashRepo.save(new ChunkHashDTO(pos, chunkHash)); - } - } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java index 13b43665e..cc73cf8fe 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java @@ -45,8 +45,7 @@ public interface IDhLevel extends AutoCloseable /** @return 0 if no hash is known */ int getChunkHash(DhChunkPos pos); - void setChunkHash(DhChunkPos pos, int chunkHash); - void updateChunkAsync(IChunkWrapper chunk); + void updateChunkAsync(IChunkWrapper chunk, int newChunkHash); void loadBeaconBeamsInPos(long pos); void setBeaconBeamsForChunk(DhChunkPos chunkPos, List beamList); From 8abefdcfd5f498841d6651e942070b127b4202f2 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 3 Aug 2024 17:11:18 -0500 Subject: [PATCH 17/27] Attempt to improve LOD building speed and reduce broken lighting on servers --- .../core/api/internal/SharedApi.java | 38 ++++++-- .../distanthorizons/core/config/Config.java | 2 + .../chunk/ChunkLightStorage.java | 19 ++++ .../chunk/IChunkWrapper.java | 91 +++++++++++++++++-- .../LightingTestChunkWrapper.java | 4 + 5 files changed, 134 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index 2fb2d6874..dd0d569e5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.level.IDhLevel; +import com.seibel.distanthorizons.core.level.IDhServerLevel; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.f3.F3Screen; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; @@ -31,6 +32,7 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo; +import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.objects.Pair; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; @@ -46,6 +48,7 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicInteger; /** Contains code and variables used by both {@link ClientApi} and {@link ServerApi} */ public class SharedApi @@ -328,29 +331,44 @@ public class SharedApi } - // Save or populate the chunk wrapper's lighting - // this is done so we don't have to worry about MC unloading the lighting data for this chunk - boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get(); - if (!onlyUseDhLighting && chunkWrapper.isLightCorrect()) + // chunk light baking is disabled since profiling revealed it used + // roughly the same amount of time as generating the lighting ourselves and + // was much more likely to have issues with corrupt (all black or all bright) chunks + boolean tryUsingMcLightingEngine = false; + if (tryUsingMcLightingEngine) { - try + // Save or populate the chunk wrapper's lighting + // this is done so we don't have to worry about MC unloading the lighting data for this chunk + boolean chunkLightPopulated = false; + boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get(); + if (!onlyUseDhLighting && chunkWrapper.isLightCorrect()) { // If MC's lighting engine isn't thread safe this may cause the server thread to lag - chunkWrapper.bakeDhLightingUsingMcLightingEngine(); + chunkLightPopulated = chunkWrapper.bakeDhLightingUsingMcLightingEngine(dhLevel.getLevelWrapper()); + if (!chunkLightPopulated) + { + // clear any existing data to prevent partial or corrupt lighting + // when re-generating it + chunkWrapper.clearDhBlockLighting(); + chunkWrapper.clearDhSkyLighting(); + } } - catch (IllegalStateException e) + + // something went wrong during the baking process so we have to generate the lighting ourselves + if (!chunkLightPopulated) { - LOGGER.warn("Chunk light baking error: " + e.getMessage(), e); + DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); } } else { - // generate the chunk's lighting, using neighboring chunks if present - DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? 15 : 0); + DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); } + + // get this chunk's active beacons List beaconBeamList = chunkWrapper.getAllActiveBeacons(nearbyChunkList); dhLevel.setBeaconBeamsForChunk(chunkWrapper.getChunkPos(), beaconBeamList); 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 828d1e5a0..b6c2448dd 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 @@ -781,8 +781,10 @@ public class Config + "") .build(); + @Deprecated public static ConfigEntry onlyUseDhLightingEngine = new ConfigEntry.Builder() .set(false) + .setAppearance(EConfigEntryAppearance.ONLY_IN_API) .comment("" + "If false LODs will be lit by Minecraft's lighting engine when possible \n" + "and fall back to the DH lighting engine only when necessary. \n" diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/ChunkLightStorage.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/ChunkLightStorage.java index 73ced41be..a17c8b06d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/ChunkLightStorage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/ChunkLightStorage.java @@ -141,6 +141,25 @@ public class ChunkLightStorage lightSection.set(x, y, z, lightLevel); } + public void clear() + { + if (this.lightSections != null) + { + for (int i = 0; i < this.lightSections.length; i++) + { + LightSection section = this.lightSections[i]; + if (section != null) + { + section.constantValue = LodUtil.MIN_MC_LIGHT; + if (section.data != null) + { + Arrays.fill(section.data, 0L); + } + } + } + } + } + //================// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java index 2ed6ad9ce..9752a2d0b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java @@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.pos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; import com.seibel.distanthorizons.core.util.LodUtil; @@ -79,9 +80,11 @@ public interface IChunkWrapper extends IBindable int getDhSkyLight(int relX, int relY, int relZ); void setDhSkyLight(int relX, int relY, int relZ, int lightValue); + void clearDhSkyLighting(); int getDhBlockLight(int relX, int relY, int relZ); void setDhBlockLight(int relX, int relY, int relZ, int lightValue); + void clearDhBlockLighting(); int getBlockLight(int relX, int relY, int relZ); int getSkyLight(int relX, int relY, int relZ); @@ -150,29 +153,77 @@ public interface IChunkWrapper extends IBindable * This is generally done in cases where MC's lighting is correct now, but may not be later (like when a chunk is unloading). * * @throws IllegalStateException if the chunk's lighting isn't valid. This is done to prevent accidentally baking broken lighting. + * @return true if the chunk's lighting was successfully populated, false otherwise */ - default void bakeDhLightingUsingMcLightingEngine() throws IllegalStateException + @Deprecated + default boolean bakeDhLightingUsingMcLightingEngine(ILevelWrapper levelWrapper) throws IllegalStateException { if (!this.isLightCorrect()) { - throw new IllegalStateException("Unable to bake lighting for for chunk [" + this.getChunkPos() + "], Minecraft lighting not valid."); + return false; } - // get the lighting for every relative block pos + //=======================// + // get lighting for each // + // relative block pos // + //=======================// + + boolean lightingFound = false; + // if the level doesn't have sky lights, then this check can be ignored + // since all sky light values will be 0 anyway + boolean skyLightingFound = !levelWrapper.hasSkyLight(); + for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++) { for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++) { for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++) { - this.setDhSkyLight(relX, y, relZ, this.getSkyLight(relX, y, relZ)); - this.setDhBlockLight(relX, y, relZ, this.getBlockLight(relX, y, relZ)); + int skyLight = this.getSkyLight(relX, y, relZ); + this.setDhSkyLight(relX, y, relZ, skyLight); + int blockLight = this.getBlockLight(relX, y, relZ); + this.setDhBlockLight(relX, y, relZ, blockLight); + + // MC defaults to max sky light and no block light, including underground blocks. + // If any position has something different then those default values, it's likely that the + // lighting was properly populated for at least part of the chunk + if (!lightingFound && + (skyLight != LodUtil.MAX_MC_LIGHT || blockLight != LodUtil.MIN_MC_LIGHT)) + { + lightingFound = true; + } + + if (!skyLightingFound + && skyLight != LodUtil.MIN_MC_LIGHT) + { + skyLightingFound = true; + } } } } + + + //=================// + // validate result // + //=================// + + // if no lighting was found or the sky is always black, the lighting is likely broken + if (!lightingFound || !skyLightingFound + // if lighting is no longer correct or doesn't match the saved values + // its very likely it broke halfway through and will need regenerating + || !this.isLightCorrect() + || this.getSkyLight(0, 0, 0) != this.getDhSkyLight(0,0,0) + || this.getBlockLight(0, 0, 0) != this.getDhBlockLight(0,0,0)) + { + return false; + } + + + // lighting is valid this.setIsDhLightCorrect(true); this.setUseDhLighting(true); + return true; } @@ -230,17 +281,37 @@ public interface IChunkWrapper extends IBindable int primeBlockMultiplier = 227; int primeBiomeMultiplier = 701; - int minBuildHeight = this.getMinBuildHeight(); - int maxBuildHeight = this.getMaxBuildHeight(); + int minBuildHeight = this.getMaxNonEmptyHeight(); + int maxBuildHeight = this.getMinNonEmptyHeight(); + + // most blocks (only some blocks are sampled since checking every block is a very slow operation) + for (int x = 0; x < LodUtil.CHUNK_WIDTH; x+=2) + { + for (int z = 0; z < LodUtil.CHUNK_WIDTH; z+=2) + { + for (int y = minBuildHeight; y < maxBuildHeight; y+=8) + { + hash = (hash * primeBlockMultiplier) + this.getBlockState(x, y, z).hashCode(); + hash = (hash * primeBiomeMultiplier) + this.getBiome(x, y, z).hashCode(); + } + } + } + + // surface (this should cover most cases for when users modify chunks) for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++) { for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++) { - for (int y = minBuildHeight; y < maxBuildHeight; y++) + int lightBlockingY = this.getLightBlockingHeightMapValue(x, z); + hash = (hash * primeBlockMultiplier) + this.getBlockState(x, lightBlockingY, z).hashCode(); + hash = (hash * primeBiomeMultiplier) + this.getBiome(x, lightBlockingY, z).hashCode(); + + int solidY = this.getSolidHeightMapValue(x, z); + if (solidY != lightBlockingY) { - hash = (hash * primeBlockMultiplier) + this.getBlockState(x, y, z).hashCode(); - hash = (hash * primeBiomeMultiplier) + this.getBiome(x, y, z).hashCode(); + hash = (hash * primeBlockMultiplier) + this.getBlockState(x, solidY, z).hashCode(); + hash = (hash * primeBiomeMultiplier) + this.getBiome(x, solidY, z).hashCode(); } } } diff --git a/core/src/test/java/testItems/lightingEngine/LightingTestChunkWrapper.java b/core/src/test/java/testItems/lightingEngine/LightingTestChunkWrapper.java index 8317a6509..142113859 100644 --- a/core/src/test/java/testItems/lightingEngine/LightingTestChunkWrapper.java +++ b/core/src/test/java/testItems/lightingEngine/LightingTestChunkWrapper.java @@ -350,6 +350,8 @@ public class LightingTestChunkWrapper implements IChunkWrapper } return this.blockLightStorage; } + @Override + public void clearDhBlockLighting() { throw new UnsupportedOperationException("Not implemented"); } @Override @@ -364,6 +366,8 @@ public class LightingTestChunkWrapper implements IChunkWrapper this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); this.getSkyLightStorage().set(relX, y, relZ, lightValue); } + @Override + public void clearDhSkyLighting() { throw new UnsupportedOperationException("Not implemented"); } private ChunkLightStorage getSkyLightStorage() { From 4ae30b3d473c3f943a0a8448b29199988a4805ab Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 4 Aug 2024 08:30:58 -0500 Subject: [PATCH 18/27] Improve LOD detail level detection and hole filling --- .../render/bufferBuilding/ColumnBox.java | 55 +++++++++++++++---- .../ColumnRenderBufferBuilder.java | 30 +++++----- .../core/render/LodRenderSection.java | 29 ++++++---- 3 files changed, 79 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnBox.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnBox.java index 1e063355f..a454bd02a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnBox.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnBox.java @@ -19,8 +19,10 @@ package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; +import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.enums.EDhDirection; +import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil; @@ -61,11 +63,11 @@ public class ColumnBox //=========// public static void addBoxQuadsToBuilder( - LodQuadBuilder builder, + LodQuadBuilder builder, IDhClientLevel clientLevel, short xSize, short ySize, short zSize, short x, short minY, short z, int color, byte irisBlockMaterialId, byte skyLight, byte blockLight, - long topData, long bottomData, ColumnArrayView[] adjData) + long topData, long bottomData, ColumnArrayView[] adjData, boolean[] isAdjDataSameDetailLevel) { //================// // variable setup // @@ -82,6 +84,15 @@ public class ColumnBox boolean isTopTransparent = RenderDataPointUtil.getAlpha(topData) < 255 && LodRenderer.transparencyEnabled; boolean isBottomTransparent = RenderDataPointUtil.getAlpha(bottomData) < 255 && LodRenderer.transparencyEnabled; + // defaulting to a value far below what we can normally render means we + // don't need to have an additional "is cave culling enabled" check + int caveCullingMaxY = Integer.MIN_VALUE; + if (Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get()) + { + caveCullingMaxY = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get() - clientLevel.getMinY(); + } + + // if there isn't any data below this LOD, make this LOD's color opaque to prevent seeing void through transparent blocks // Note: this LOD should still be considered transparent for this method's checks, otherwise rendering bugs may occur @@ -135,12 +146,11 @@ public class ColumnBox // NORTH face { ColumnArrayView adjCol = adjData[EDhDirection.NORTH.ordinal() - 2]; // TODO can we use something other than ordinal-2? - // if the adjacent column is null that generally means it's representing a different detail level + boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.NORTH.ordinal() - 2]; + // if the adjacent column is null that generally means the adjacent area hasn't been generated yet if (adjCol == null) { // Add an adjacent face if this is opaque face or transparent over the void. - // By skipping transparent faces that aren't over the void we prevent adding ocean faces - // between detail levels. if (!isTransparent || overVoid) { builder.addQuadAdj(EDhDirection.NORTH, x, minY, z, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); @@ -148,7 +158,7 @@ public class ColumnBox } else { - makeAdjVerticalQuad(builder, adjCol, EDhDirection.NORTH, x, minY, z, xSize, ySize, + makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH, x, minY, z, xSize, ySize, color, irisBlockMaterialId, blockLight); } } @@ -156,6 +166,7 @@ public class ColumnBox // SOUTH face { ColumnArrayView adjCol = adjData[EDhDirection.SOUTH.ordinal() - 2]; + boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.SOUTH.ordinal() - 2]; if (adjCol == null) { if (!isTransparent || overVoid) @@ -165,7 +176,7 @@ public class ColumnBox } else { - makeAdjVerticalQuad(builder, adjCol, EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize, + makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize, color, irisBlockMaterialId, blockLight); } } @@ -173,6 +184,7 @@ public class ColumnBox // WEST face { ColumnArrayView adjCol = adjData[EDhDirection.WEST.ordinal() - 2]; + boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.WEST.ordinal() - 2]; if (adjCol == null) { if (!isTransparent || overVoid) @@ -182,7 +194,7 @@ public class ColumnBox } else { - makeAdjVerticalQuad(builder, adjCol, EDhDirection.WEST, x, minY, z, zSize, ySize, + makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST, x, minY, z, zSize, ySize, color, irisBlockMaterialId, blockLight); } } @@ -190,6 +202,7 @@ public class ColumnBox // EAST face { ColumnArrayView adjCol = adjData[EDhDirection.EAST.ordinal() - 2]; + boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.EAST.ordinal() - 2]; if (adjCol == null) { if (!isTransparent || overVoid) @@ -199,14 +212,14 @@ public class ColumnBox } else { - makeAdjVerticalQuad(builder, adjCol, EDhDirection.EAST, maxX, minY, z, zSize, ySize, + makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST, maxX, minY, z, zSize, ySize, color, irisBlockMaterialId, blockLight); } } } private static void makeAdjVerticalQuad( - LodQuadBuilder builder, @NotNull ColumnArrayView adjColumnView, EDhDirection direction, + LodQuadBuilder builder, @NotNull ColumnArrayView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction, short x, short yMin, short z, short horizontalWidth, short ySize, int color, byte irisBlockMaterialId, byte blockLight) { @@ -283,10 +296,30 @@ public class ColumnBox { // adj opaque // mark positions adjacent is covering + byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint); for (int i = adjMinY; i < adjMaxY; i++) { byte skyLightAtPos = skyLightAtInputPos[i]; - skyLightAtInputPos[i] = (byte) Math.min(SKYLIGHT_COVERED, skyLightAtPos); + + // if the adjacent is a different detail level, we want to render adjacent opaque + // faces to try and reduce the chance of holes on detail level borders + boolean adjacentCoversThis = + // if the adjacent is the same detail level, no special handling is necessary + !adjacentIsSameDetailLevel + // if the adjacent face is underground we probably don't need it + && RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY + // check if this face is on a border + && + ( + (x == 0 && direction == EDhDirection.WEST) + || (z == 0 && direction == EDhDirection.NORTH) + // TODO why does 256 represent a border? aren't LODs only 64 datapoints wide? + || (x == 256 && direction == EDhDirection.EAST) + || (z == 256 && direction == EDhDirection.SOUTH) + ); + + byte newSkyLightAtPos = adjacentCoversThis ? adjSkyLight : SKYLIGHT_COVERED; + skyLightAtInputPos[i] = (byte) Math.min(newSkyLightAtPos, skyLightAtPos); } } else 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 9276f135d..78fabc59c 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 @@ -63,7 +63,7 @@ public class ColumnRenderBufferBuilder public static CompletableFuture buildBuffersAsync( IDhClientLevel clientLevel, - ColumnRenderSource renderSource, ColumnRenderSource[] adjData + ColumnRenderSource renderSource, ColumnRenderSource[] adjData, boolean[] isSameDetailLevel ) { ThreadPoolExecutor bufferBuilderExecutor = ThreadPoolUtil.getBufferBuilderExecutor(); @@ -84,7 +84,7 @@ public class ColumnRenderBufferBuilder { boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled; LodQuadBuilder builder = new LodQuadBuilder(enableTransparency, clientLevel.getClientLevelWrapper()); - makeLodRenderData(builder, renderSource, adjData); + makeLodRenderData(builder, renderSource, clientLevel, adjData, isSameDetailLevel); return builder; } catch (UncheckedInterruptedException e) @@ -172,7 +172,9 @@ public class ColumnRenderBufferBuilder return future; } } - private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, ColumnRenderSource[] adjRegions) + private static void makeLodRenderData( + LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel, + ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel) { //=============// // debug check // @@ -362,8 +364,9 @@ public class ColumnRenderBufferBuilder long bottomDataPoint = (i + 1) < columnRenderData.size() ? columnRenderData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA; addLodToBuffer( + clientLevel, data, topDataPoint, bottomDataPoint, - adjColumnViews, + adjColumnViews, isSameDetailLevel, thisDetailLevel, relX, relZ, quadBuilder, debugSourceFlag); } @@ -374,8 +377,9 @@ public class ColumnRenderBufferBuilder quadBuilder.finalizeData(); } private static void addLodToBuffer( + IDhClientLevel clientLevel, long data, long topData, long bottomData, - ColumnArrayView[] adjColumnViews, + ColumnArrayView[] adjColumnViews, boolean[] isSameDetailLevel, byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ, LodQuadBuilder quadBuilder, ColumnRenderSource.DebugSourceFlag debugSource) { @@ -505,14 +509,14 @@ public class ColumnRenderBufferBuilder } ColumnBox.addBoxQuadsToBuilder( - quadBuilder, // buffer - width, ySize, width, // setWidth - x, yMin, z, // setOffset - color, // setColor - blockMaterialId, // irisBlockMaterialId - RenderDataPointUtil.getLightSky(data), // setSkyLights - fullBright ? 15 : RenderDataPointUtil.getLightBlock(data), // setBlockLights - topData, bottomData, adjColumnViews); // setAdjData + quadBuilder, clientLevel, + width, ySize, width, + x, yMin, z, + color, + blockMaterialId, + RenderDataPointUtil.getLightSky(data), + fullBright ? 15 : RenderDataPointUtil.getLightBlock(data), + topData, bottomData, adjColumnViews, isSameDetailLevel); } } 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 f8ddf4dee..b06ff485a 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 @@ -202,9 +202,15 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable ColumnRenderBuffer previousBuffer = this.renderBuffer; ColumnRenderSource[] adjacentRenderSections = new ColumnRenderSource[EDhDirection.ADJ_DIRECTIONS.length]; + boolean[] adjIsSameDetailLevel = new boolean[EDhDirection.ADJ_DIRECTIONS.length]; for (int i = 0; i < EDhDirection.ADJ_DIRECTIONS.length; i++) { adjacentRenderSections[i] = adjRenderSourceLoadRefFutures[i].future.getNow(null); + + // if the adjacent position isn't the same detail level the buffer building logic + // will need to be slightly different in order to reduce holes in the LODs + EDhDirection direction = EDhDirection.ADJ_DIRECTIONS[i]; + adjIsSameDetailLevel[direction.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(direction); } if (this.bufferBuildFuture != null) @@ -213,7 +219,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable // prevents the CPU from working on something that won't be used this.bufferBuildFuture.cancel(true); } - this.bufferBuildFuture = ColumnRenderBufferBuilder.buildBuffersAsync(this.level, renderSource, adjacentRenderSections); + this.bufferBuildFuture = ColumnRenderBufferBuilder.buildBuffersAsync(this.level, renderSource, adjacentRenderSections, adjIsSameDetailLevel); this.bufferBuildFuture.thenAccept((lodQuadBuilder) -> { @@ -283,17 +289,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable long adjPos = DhSectionPos.getAdjacentPos(this.pos, direction); try { - // ignore adjacent positions that aren't the same detail level - // since the LodDataBuilder can't handle different detail levels - byte detailLevel = this.quadTree.calculateExpectedDetailLevel(new DhBlockPos2D(MC.getPlayerBlockPos()), adjPos); - detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; - if (detailLevel == DhSectionPos.getDetailLevel(this.pos)) + LodRenderSection adjRenderSection = this.quadTree.getValue(adjPos); + if (adjRenderSection != null) { - LodRenderSection adjRenderSection = this.quadTree.getValue(adjPos); - if (adjRenderSection != null) - { - futureArray[arrayIndex] = adjRenderSection.getRenderSourceAsync(); - } + futureArray[arrayIndex] = adjRenderSection.getRenderSourceAsync(); } } catch (IndexOutOfBoundsException ignore) {} @@ -354,6 +353,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable this.getRenderSourceLock.unlock(); } } + private boolean isAdjacentPosSameDetailLevel(EDhDirection direction) + { + long adjPos = DhSectionPos.getAdjacentPos(this.pos, direction); + byte detailLevel = this.quadTree.calculateExpectedDetailLevel(new DhBlockPos2D(MC.getPlayerBlockPos()), adjPos); + detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; + boolean adjacentIsSameDetailLevel = (detailLevel == DhSectionPos.getDetailLevel(this.pos)); + return adjacentIsSameDetailLevel; + } /** From f05eac2637fcdf5b4c30e95ae50fc73e272ea3ba Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 6 Aug 2024 20:47:23 -0500 Subject: [PATCH 19/27] test --- .../render/bufferBuilding/ColumnRenderBuffer.java | 5 +++++ .../render/bufferBuilding/LodQuadBuilder.java | 9 ++++----- .../render/glObject/buffer/QuadElementBuffer.java | 5 ++++- .../core/render/renderer/ScreenQuad.java | 5 +++-- .../renderer/generic/GenericObjectRenderer.java | 11 +++++------ 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java index cea580bc5..6b6807787 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java @@ -33,6 +33,7 @@ import com.seibel.distanthorizons.core.util.objects.StatsMap; import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import org.apache.logging.log4j.Logger; +import org.lwjgl.system.MemoryUtil; import java.nio.ByteBuffer; import java.util.Iterator; @@ -220,6 +221,10 @@ public class ColumnRenderBuffer implements AutoCloseable vbos[vboIndex] = null; vbo.close(); LOGGER.error("Failed to upload buffer: ", e); + } + finally + { + MemoryUtil.memFree(bb); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java index b9db5c836..12e2daf01 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java @@ -38,6 +38,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.coreapi.util.MathUtil; import org.apache.logging.log4j.Logger; +import org.lwjgl.system.MemoryUtil; /** * Used to create the quads before they are converted to render-able buffers.

@@ -414,9 +415,8 @@ public class LodQuadBuilder { return new Iterator() { - final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER) - .order(ByteOrder.nativeOrder()); - int dir = skipEmpty(0); + final ByteBuffer bb = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER); + int dir = this.skipEmpty(0); int quad = 0; private int skipEmpty(int d) @@ -482,8 +482,7 @@ public class LodQuadBuilder { return new Iterator() { - final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER) - .order(ByteOrder.nativeOrder()); + final ByteBuffer bb = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER); int directionIndex = this.skipEmptyDirectionIndices(0); int quad = 0; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/QuadElementBuffer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/QuadElementBuffer.java index 915d70c3d..f01ff0abd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/QuadElementBuffer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/QuadElementBuffer.java @@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.render.glObject.GLEnums; import com.seibel.distanthorizons.core.render.glObject.GLProxy; import org.apache.logging.log4j.Logger; import org.lwjgl.opengl.GL32; +import org.lwjgl.system.MemoryUtil; import java.lang.invoke.MethodHandles; import java.nio.ByteBuffer; @@ -154,7 +155,7 @@ public class QuadElementBuffer extends GLElementBuffer LOGGER.info("Quad IBO Resizing from [" + getCapacity() + "] to [" + quadCount + "]" + " with type: " + GLEnums.getString(type)); - ByteBuffer buffer = ByteBuffer.allocateDirect(indicesCount * GLEnums.getTypeSize(type)).order(ByteOrder.nativeOrder()); + ByteBuffer buffer = MemoryUtil.memAlloc(indicesCount * GLEnums.getTypeSize(type)); buildBuffer(quadCount, buffer, type); if (!gl.bufferStorageSupported) { @@ -169,6 +170,8 @@ public class QuadElementBuffer extends GLElementBuffer super.uploadBuffer(buffer, EDhApiGpuUploadMethod.BUFFER_STORAGE, indicesCount * GLEnums.getTypeSize(type), 0); } + + MemoryUtil.memFree(buffer); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/ScreenQuad.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/ScreenQuad.java index 2f9abd294..b551ffcdd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/ScreenQuad.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/ScreenQuad.java @@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer; import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.AbstractVertexAttribute; import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexPointer; import org.lwjgl.opengl.GL32; +import org.lwjgl.system.MemoryUtil; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -86,14 +87,14 @@ public class ScreenQuad private void createBuffer() { - ByteBuffer buffer = ByteBuffer.allocateDirect(box_vertices.length * Float.BYTES); - buffer.order(ByteOrder.nativeOrder()); + ByteBuffer buffer = MemoryUtil.memAlloc(box_vertices.length * Float.BYTES); buffer.asFloatBuffer().put(box_vertices); buffer.rewind(); this.boxBuffer = new GLVertexBuffer(false); this.boxBuffer.bind(); this.boxBuffer.uploadBuffer(buffer, box_vertices.length, EDhApiGpuUploadMethod.DATA, box_vertices.length * Float.BYTES); + MemoryUtil.memFree(buffer); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/GenericObjectRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/GenericObjectRenderer.java index b3768548d..41baf1522 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/GenericObjectRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/GenericObjectRenderer.java @@ -50,6 +50,7 @@ import org.apache.logging.log4j.Logger; import org.lwjgl.opengl.ARBInstancedArrays; import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL33; +import org.lwjgl.system.MemoryUtil; import java.awt.*; import java.nio.ByteBuffer; @@ -192,24 +193,22 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister private void createBuffers() { // box vertices - ByteBuffer boxVerticesBuffer = ByteBuffer.allocateDirect(BOX_VERTICES.length * Float.BYTES); - boxVerticesBuffer.order(ByteOrder.nativeOrder()); + ByteBuffer boxVerticesBuffer = MemoryUtil.memAlloc(BOX_VERTICES.length * Float.BYTES); boxVerticesBuffer.asFloatBuffer().put(BOX_VERTICES); boxVerticesBuffer.rewind(); this.boxVertexBuffer = new GLVertexBuffer(false); this.boxVertexBuffer.bind(); this.boxVertexBuffer.uploadBuffer(boxVerticesBuffer, 8, EDhApiGpuUploadMethod.DATA, BOX_VERTICES.length * Float.BYTES); - + MemoryUtil.memFree(boxVerticesBuffer); // box vertex indexes - ByteBuffer solidIndexBuffer = ByteBuffer.allocateDirect(BOX_INDICES.length * Integer.BYTES); - solidIndexBuffer.order(ByteOrder.nativeOrder()); + ByteBuffer solidIndexBuffer = MemoryUtil.memAlloc(BOX_INDICES.length * Integer.BYTES); solidIndexBuffer.asIntBuffer().put(BOX_INDICES); solidIndexBuffer.rewind(); this.boxIndexBuffer = new GLElementBuffer(false); this.boxIndexBuffer.uploadBuffer(solidIndexBuffer, EDhApiGpuUploadMethod.DATA, BOX_INDICES.length * Integer.BYTES, GL32.GL_STATIC_DRAW); this.boxIndexBuffer.bind(); - + MemoryUtil.memFree(solidIndexBuffer); } private void addGenericDebugObjects() { From a8df13fdd27734871d56d43e2917557f212e86e1 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 6 Aug 2024 21:47:03 -0500 Subject: [PATCH 20/27] Fix LodRenderSection holding onto unnecessary memory --- .../seibel/distanthorizons/core/render/LodRenderSection.java | 5 +++++ 1 file changed, 5 insertions(+) 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 b06ff485a..ec71b3419 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 @@ -188,6 +188,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable // nothing needs to be rendered this.canRender = false; this.buildAndUploadRenderDataToGpuFuture = null; + this.bufferBuildFuture = null; return; } @@ -242,6 +243,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable this.renderBuffer = buffer; this.canRender = (buffer != null); this.buildAndUploadRenderDataToGpuFuture = null; + this.bufferBuildFuture = null; if (previousBuffer != null) @@ -263,6 +265,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e); this.buildAndUploadRenderDataToGpuFuture = null; + this.bufferBuildFuture = null; } } catch (Exception e) @@ -273,6 +276,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e); this.buildAndUploadRenderDataToGpuFuture = null; + this.bufferBuildFuture = null; } }); }, executor); @@ -371,6 +375,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable { CompletableFuture future = this.buildAndUploadRenderDataToGpuFuture; this.buildAndUploadRenderDataToGpuFuture = null; + this.bufferBuildFuture = null; if (future != null) { // interrupting the future speeds things up, but also causes From 90fdfbbe61dcc3258c48c6c7a20cf4704449e5a7 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Wed, 7 Aug 2024 07:29:52 -0500 Subject: [PATCH 21/27] Fix direct memory leak and remove config for GpuUpload --- .../enums/config/EDhApiGpuUploadMethod.java | 6 +- .../api/interfaces/config/IDhApiConfig.java | 1 - .../config/client/IDhApiGpuBuffersConfig.java | 48 -- .../external/methods/config/DhApiConfig.java | 2 - .../config/client/DhApiGpuBuffersConfig.java | 42 -- .../distanthorizons/core/config/Config.java | 48 -- .../bufferBuilding/ColumnRenderBuffer.java | 112 ++-- .../render/bufferBuilding/LodQuadBuilder.java | 508 +++++------------- .../core/render/glObject/GLProxy.java | 12 +- .../core/render/glObject/buffer/GLBuffer.java | 4 +- .../glObject/buffer/GLVertexBuffer.java | 4 +- 11 files changed, 161 insertions(+), 626 deletions(-) delete mode 100644 api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGpuBuffersConfig.java delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGpuBuffersConfig.java diff --git a/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EDhApiGpuUploadMethod.java b/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EDhApiGpuUploadMethod.java index df4c4a767..8b6b737f8 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EDhApiGpuUploadMethod.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EDhApiGpuUploadMethod.java @@ -23,13 +23,12 @@ package com.seibel.distanthorizons.api.enums.config; * AUTO,
* BUFFER_STORAGE,
* SUB_DATA,
- * BUFFER_MAPPING,
* DATA
* * @author Leetom * @author James Seibel * @version 2024-4-6 - * @since API 2.0.0 + * @since API 3.0.0 */ public enum EDhApiGpuUploadMethod { @@ -49,7 +48,10 @@ public enum EDhApiGpuUploadMethod * May end up storing buffers in System memory.
* Fast rending if in GPU memory, slow if in system memory,
* but won't stutter when uploading. + * + * @deprecated not currently supported */ + @Deprecated BUFFER_MAPPING(true, false), /** Fast rendering but may stutter when uploading. */ diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/IDhApiConfig.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/IDhApiConfig.java index a0f81b499..fd7a5b8b4 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/IDhApiConfig.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/IDhApiConfig.java @@ -36,7 +36,6 @@ public interface IDhApiConfig IDhApiWorldGenerationConfig worldGenerator(); IDhApiMultiplayerConfig multiplayer(); IDhApiMultiThreadingConfig multiThreading(); - IDhApiGpuBuffersConfig gpuBuffers(); // note: DON'T add the Auto Updater to this API. We only want the user's to have the ability to control when things are downloaded to their machines. //IDhApiLoggingConfig logging(); // TODO implement IDhApiDebuggingConfig debugging(); diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGpuBuffersConfig.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGpuBuffersConfig.java deleted file mode 100644 index 4b0433f7a..000000000 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGpuBuffersConfig.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of the Distant Horizons mod - * licensed under the GNU LGPL v3 License. - * - * Copyright (C) 2020-2023 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.seibel.distanthorizons.api.interfaces.config.client; - -import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; -import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup; -import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue; - -/** - * Distant Horizons' OpenGL buffer configuration. - * - * @author James Seibel - * @version 2023-6-14 - * @since API 1.0.0 - */ -public interface IDhApiGpuBuffersConfig extends IDhApiConfigGroup -{ - - /** Defines how geometry data is uploaded to the GPU. */ - IDhApiConfigValue gpuUploadMethod(); - - /** - * Defines how long we should wait after uploading one - * Megabyte of geometry data to the GPU before uploading - * the next Megabyte of data.
- * This can be set to a non-zero number to reduce stuttering caused by - * uploading buffers to the GPU. - */ - IDhApiConfigValue gpuUploadPerMegabyteInMilliseconds(); - -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/DhApiConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/DhApiConfig.java index 5197f7b46..1abbf6d32 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/DhApiConfig.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/DhApiConfig.java @@ -42,8 +42,6 @@ public class DhApiConfig implements IDhApiConfig @Override public IDhApiMultiThreadingConfig multiThreading() { return DhApiMultiThreadingConfig.INSTANCE; } @Override - public IDhApiGpuBuffersConfig gpuBuffers() { return DhApiGpuBuffersConfig.INSTANCE; } - @Override public IDhApiDebuggingConfig debugging() { return DhApiDebuggingConfig.INSTANCE; } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGpuBuffersConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGpuBuffersConfig.java deleted file mode 100644 index 22f4852b0..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGpuBuffersConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This file is part of the Distant Horizons mod - * licensed under the GNU LGPL v3 License. - * - * Copyright (C) 2020-2023 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.seibel.distanthorizons.core.api.external.methods.config.client; - -import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue; -import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiGpuBuffersConfig; -import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue; -import com.seibel.distanthorizons.core.config.Config; -import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; - -public class DhApiGpuBuffersConfig implements IDhApiGpuBuffersConfig -{ - public static DhApiGpuBuffersConfig INSTANCE = new DhApiGpuBuffersConfig(); - - private DhApiGpuBuffersConfig() { } - - - - public IDhApiConfigValue gpuUploadMethod() - { return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadMethod); } - - public IDhApiConfigValue gpuUploadPerMegabyteInMilliseconds() - { return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds); } - -} 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 b6c2448dd..463ebcbea 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 @@ -125,7 +125,6 @@ public class Config public static ConfigCategory multiplayer = new ConfigCategory.Builder().set(Multiplayer.class).build(); public static ConfigCategory lodBuilding = new ConfigCategory.Builder().set(LodBuilding.class).build(); public static ConfigCategory multiThreading = new ConfigCategory.Builder().set(MultiThreading.class).build(); - public static ConfigCategory buffers = new ConfigCategory.Builder().set(GpuBuffers.class).build(); public static ConfigCategory autoUpdater = new ConfigCategory.Builder().set(AutoUpdater.class).build(); public static ConfigCategory logging = new ConfigCategory.Builder().set(Logging.class).build(); @@ -1072,53 +1071,6 @@ public class Config } - public static class GpuBuffers - { - public static ConfigEntry gpuUploadMethod = new ConfigEntry.Builder() - .set(EDhApiGpuUploadMethod.AUTO) - .comment("" - + "What method should be used to upload geometry to the GPU? \n" - + "\n" - + EDhApiGpuUploadMethod.AUTO + ": Picks the best option based on the GPU you have. \n" - + "\n" - + EDhApiGpuUploadMethod.BUFFER_STORAGE + ": Default if OpenGL 4.5 is supported. \n" - + " Fast rendering, no stuttering. \n" - + "\n" - + EDhApiGpuUploadMethod.SUB_DATA + ": Backup option for NVIDIA. \n" - + " Fast rendering but may stutter when uploading. \n" - + "\n" - + EDhApiGpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. \n" - + " Generally the best option for integrated GPUs. \n" - + " Default option for AMD/Intel if OpenGL 4.5 isn't supported. \n" - + " May end up storing buffers in System memory. \n" - + " Fast rendering if in GPU memory, slow if in system memory, \n" - + " but won't stutter when uploading. \n" - + "\n" - + EDhApiGpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n" - + " Backup option for AMD/Intel. \n" - + " Fast rendering but may stutter when uploading. \n" - + "\n" - + "If you don't see any difference when changing these settings, \n" - + "or the world looks corrupted: restart your game." - + "") - .build(); - - public static ConfigEntry gpuUploadPerMegabyteInMilliseconds = new ConfigEntry.Builder() - .setMinDefaultMax(0, 0, 50) - .comment("" - + "How long should a buffer wait per Megabyte of data uploaded? \n" - + "Helpful resource for frame times: https://fpstoms.com \n" - + "\n" - + "Longer times may reduce stuttering but will make LODs \n" - + "transition and load slower. Change this to [0] for no timeout. \n" - + "\n" - + "NOTE:\n" - + "Before changing this config, try changing the \"GPU Upload method\" first. \n" - + "") - .build(); - - } - public static class AutoUpdater { public static ConfigEntry enableAutoUpdater = new ConfigEntry.Builder() diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java index 6b6807787..833e0cf8b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java @@ -36,7 +36,7 @@ import org.apache.logging.log4j.Logger; import org.lwjgl.system.MemoryUtil; import java.nio.ByteBuffer; -import java.util.Iterator; +import java.util.ArrayList; import java.util.concurrent.*; /** @@ -101,7 +101,7 @@ public class ColumnRenderBuffer implements AutoCloseable { try { - this.uploadBuffersUsingUploadMethod(builder, gpuUploadMethod); + this.uploadBuffers(builder, gpuUploadMethod); uploadFuture.complete(null); } catch (InterruptedException e) @@ -127,72 +127,46 @@ public class ColumnRenderBuffer implements AutoCloseable //LOGGER.warn("Error uploading builder ["+builder+"] synchronously. Error: "+e.getMessage(), e); } } - private void uploadBuffersUsingUploadMethod(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException + private void uploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException { - if (gpuUploadMethod.useEarlyMapping) - { - this.uploadBuffersMapped(builder, gpuUploadMethod); - } - else - { - this.uploadBuffersDirect(builder, gpuUploadMethod); - } + // uploading mapped buffers used to be done here, + // however due to a memory leak and complication with the previous code, + // now we only allow direct uploading. + // (There's also insufficient data to state whether mapped buffers are necessary + // for DH to upload without stuttering the main thread) + + this.vbos = makeAndUploadBuffers(builder, method, this.vbos, builder.makeOpaqueVertexBuffers()); + this.vbosTransparent = makeAndUploadBuffers(builder, method, this.vbosTransparent, builder.makeTransparentVertexBuffers()); this.buffersUploaded = true; } - - - - private void uploadBuffersMapped(LodQuadBuilder builder, EDhApiGpuUploadMethod method) + /** This resizes and returns the vbo array if necessary based on the amount of data needed for this area. */ + private static GLVertexBuffer[] makeAndUploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method, GLVertexBuffer[] vbos, ArrayList buffers) throws InterruptedException { - // opaque vbos // - - this.vbos = resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount()); - for (int i = 0; i < this.vbos.length; i++) + try { - if (this.vbos[i] == null) + vbos = resizeBuffer(vbos, buffers.size()); + uploadBuffersDirect(vbos, buffers, method); + } + finally + { + // all the buffers must be manually freed to prevent memory leaks + if (buffers != null) { - this.vbos[i] = new GLVertexBuffer(method.useBufferStorage); + for (ByteBuffer buffer : buffers) + { + MemoryUtil.memFree(buffer); + } } } - LodQuadBuilder.BufferFiller func = builder.makeOpaqueBufferFiller(method); - for (GLVertexBuffer vbo : this.vbos) - { - func.fill(vbo); - } - - // transparent vbos // - - this.vbosTransparent = resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount()); - for (int i = 0; i < this.vbosTransparent.length; i++) - { - if (this.vbosTransparent[i] == null) - { - this.vbosTransparent[i] = new GLVertexBuffer(method.useBufferStorage); - } - } - LodQuadBuilder.BufferFiller transparentFillerFunc = builder.makeTransparentBufferFiller(method); - for (GLVertexBuffer vbo : this.vbosTransparent) - { - transparentFillerFunc.fill(vbo); - } + // return the array in case it was resized + return vbos; } - - private void uploadBuffersDirect(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException + private static void uploadBuffersDirect(GLVertexBuffer[] vbos, ArrayList byteBuffers, EDhApiGpuUploadMethod method) throws InterruptedException { - this.vbos = resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount()); - uploadBuffersDirect(this.vbos, builder.makeOpaqueVertexBuffers(), method); - - this.vbosTransparent = resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount()); - uploadBuffersDirect(this.vbosTransparent, builder.makeTransparentVertexBuffers(), method); - } - private static void uploadBuffersDirect(GLVertexBuffer[] vbos, Iterator iter, EDhApiGpuUploadMethod method) throws InterruptedException - { - long remainingMS = 0; - long MBPerMS = Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds.get(); int vboIndex = 0; - while (iter.hasNext()) + for (int i = 0; i < byteBuffers.size(); i++) { if (vboIndex >= vbos.length) { @@ -208,41 +182,19 @@ public class ColumnRenderBuffer implements AutoCloseable GLVertexBuffer vbo = vbos[vboIndex]; - ByteBuffer bb = iter.next(); - int size = bb.limit() - bb.position(); + ByteBuffer buffer = byteBuffers.get(i); + int size = buffer.limit() - buffer.position(); try { vbo.bind(); - vbo.uploadBuffer(bb, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER); + vbo.uploadBuffer(buffer, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER); } catch (Exception e) { vbos[vboIndex] = null; vbo.close(); LOGGER.error("Failed to upload buffer: ", e); - } - finally - { - MemoryUtil.memFree(bb); - } - - - if (MBPerMS > 0) - { - // upload buffers over an extended period of time - // to hopefully prevent stuttering. - remainingMS += size * MBPerMS; - if (remainingMS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS)) - { - if (remainingMS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS) - { - remainingMS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS; - } - - Thread.sleep(remainingMS / 1000000, (int) (remainingMS % 1000000)); - remainingMS = 0; - } } vboIndex++; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java index 12e2daf01..89cbcfec3 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java @@ -206,10 +206,124 @@ public class LodQuadBuilder + //=================// + // data finalizing // + //=================// + + /** runs any final data cleanup, merging, etc. */ + public void finalizeData() { this.mergeQuads(); } + + /** Uses Greedy meshing to merge this builder's Quads. */ + public void mergeQuads() + { + long mergeCount = 0; // can be used for debugging + long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount(); + if (preQuadsCount <= 1) + { + return; + } + + for (int directionIndex = 0; directionIndex < 6; directionIndex++) + { + mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest); + if (this.doTransparency) + { + mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest); + } + + + // only run the second merge if the face is the top or bottom + if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal()) + { + mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown); + if (this.doTransparency) + { + mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown); + } + } + } + + //long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount(); + //LOGGER.trace("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads"); + } + + /** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */ + private static long mergeQuadsInternal(ArrayList[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection) + { + if (list[directionIndex].size() <= 1) + return 0; + + list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection)); + + long mergeCount = 0; + ListIterator iter = list[directionIndex].listIterator(); + BufferQuad currentQuad = iter.next(); + while (iter.hasNext()) + { + BufferQuad nextQuad = iter.next(); + + if (currentQuad.tryMerge(nextQuad, mergeDirection)) + { + // merge successful, attempt to merge the next quad + mergeCount++; + iter.set(null); + } + else + { + // merge fail, move on to the next quad + currentQuad = nextQuad; + } + } + list[directionIndex].removeIf(Objects::isNull); + return mergeCount; + } + + + //==============// - // add vertices // + // buffer setup // //==============// + public ArrayList makeOpaqueVertexBuffers() { return this.makeVertexBuffers(this.opaqueQuads); } + public ArrayList makeTransparentVertexBuffers() { return this.makeVertexBuffers(this.transparentQuads); } + private ArrayList makeVertexBuffers(ArrayList[] quadList) + { + ArrayList byteBufferList = new ArrayList<>(3); + + ByteBuffer buffer = null; + for (int directionIndex = 0; directionIndex < 6; directionIndex++) + { + // ignore empty directions + if (quadList[directionIndex].isEmpty()) + { + continue; + } + + // put all the quads in this direction into the buffer + for (int quadIndex = 0; quadIndex < quadList[directionIndex].size(); quadIndex++) + { + // if this is the first iteration or the buffer is full, + // create a new buffer + if (buffer == null || !buffer.hasRemaining()) + { + buffer = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER); + byteBufferList.add(buffer); + } + + this.putQuad(buffer, quadList[directionIndex].get(quadIndex)); + } + } + + // rewind all the buffers so they can be read from + for (int i = 0; i < byteBufferList.size(); i++) + { + buffer = byteBufferList.get(i); + buffer.limit(buffer.position()); + buffer.rewind(); + } + + return byteBufferList; + } private void putQuad(ByteBuffer bb, BufferQuad quad) { int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()]; @@ -267,10 +381,10 @@ public class LodQuadBuilder if (quad.direction.getAxis().isHorizontal() || quad.direction == EDhDirection.DOWN) { if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT - // if we want the color to fade, only apply the dirt color to the bottom vertices - || (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0) - // always render the bottom as dirt - || quad.direction == EDhDirection.DOWN) + // if we want the color to fade, only apply the dirt color to the bottom vertices + || (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0) + // always render the bottom as dirt + || quad.direction == EDhDirection.DOWN) { // for horizontal and bottom faces of grass blocks, use the dirt color to // prevent green cliff walls @@ -278,7 +392,7 @@ public class LodQuadBuilder color = ColorUtil.applyShade(color, MC.getShade(quad.direction)); } } - } + } } } @@ -292,7 +406,6 @@ public class LodQuadBuilder mx, my, mz); } } - private void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte normalIndex, byte irisBlockMaterialId, byte skylight, byte blocklight, int mx, int my, int mz) { skylight %= 16; @@ -333,387 +446,6 @@ public class LodQuadBuilder - //=================// - // data finalizing // - //=================// - - /** runs any final data cleanup, merging, etc. */ - public void finalizeData() { this.mergeQuads(); } - - /** Uses Greedy meshing to merge this builder's Quads. */ - public void mergeQuads() - { - long mergeCount = 0; - long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount(); - if (preQuadsCount <= 1) - { - return; - } - - for (int directionIndex = 0; directionIndex < 6; directionIndex++) - { - mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest); - if (this.doTransparency) - { - mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest); - } - - - // only run the second merge if the face is the top or bottom - if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal()) - { - mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown); - if (this.doTransparency) - { - mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown); - } - } - } - - long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount(); - LOGGER.debug("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads"); - } - - /** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */ - private static long mergeQuadsInternal(ArrayList[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection) - { - if (list[directionIndex].size() <= 1) - return 0; - - list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection)); - - long mergeCount = 0; - ListIterator iter = list[directionIndex].listIterator(); - BufferQuad currentQuad = iter.next(); - while (iter.hasNext()) - { - BufferQuad nextQuad = iter.next(); - - if (currentQuad.tryMerge(nextQuad, mergeDirection)) - { - // merge successful, attempt to merge the next quad - mergeCount++; - iter.set(null); - } - else - { - // merge fail, move on to the next quad - currentQuad = nextQuad; - } - } - list[directionIndex].removeIf(Objects::isNull); - return mergeCount; - } - - - - //==============// - // buffer setup // - //==============// - - public Iterator makeOpaqueVertexBuffers() - { - return new Iterator() - { - final ByteBuffer bb = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER); - int dir = this.skipEmpty(0); - int quad = 0; - - private int skipEmpty(int d) - { - while (d < 6 && opaqueQuads[d].isEmpty()) - { - d++; - } - return d; - } - - @Override - public boolean hasNext() - { - return dir < 6; - } - - @Override - public ByteBuffer next() - { - if (dir >= 6) - { - return null; - } - bb.clear(); - bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER); - while (bb.hasRemaining() && dir < 6) - { - writeData(); - } - bb.limit(bb.position()); - bb.rewind(); - return bb; - } - - private void writeData() - { - int i = quad; - for (; i < opaqueQuads[dir].size(); i++) - { - if (!bb.hasRemaining()) - { - break; - } - putQuad(bb, opaqueQuads[dir].get(i)); - } - - if (i >= opaqueQuads[dir].size()) - { - quad = 0; - dir++; - dir = skipEmpty(dir); - } - else - { - quad = i; - } - } - }; - } - - public Iterator makeTransparentVertexBuffers() - { - return new Iterator() - { - final ByteBuffer bb = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER); - int directionIndex = this.skipEmptyDirectionIndices(0); - int quad = 0; - - private int skipEmptyDirectionIndices(int directionIndex) - { - while (directionIndex < 6 && - (LodQuadBuilder.this.transparentQuads[directionIndex] == null - || LodQuadBuilder.this.transparentQuads[directionIndex].isEmpty())) - { - directionIndex++; - } - - return directionIndex; - } - - @Override - public boolean hasNext() { return this.directionIndex < 6; } - - @Override - public ByteBuffer next() - { - if (this.directionIndex >= 6) - { - return null; - } - - this.bb.clear(); - this.bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER); - while (this.bb.hasRemaining() && this.directionIndex < 6) - { - this.writeData(); - } - this.bb.limit(this.bb.position()); - this.bb.rewind(); - return this.bb; - } - - private void writeData() - { - int i = this.quad; - for (; i < LodQuadBuilder.this.transparentQuads[this.directionIndex].size(); i++) - { - if (!this.bb.hasRemaining()) - { - break; - } - putQuad(this.bb, LodQuadBuilder.this.transparentQuads[this.directionIndex].get(i)); - } - - if (i >= LodQuadBuilder.this.transparentQuads[this.directionIndex].size()) - { - this.quad = 0; - this.directionIndex++; - this.directionIndex = this.skipEmptyDirectionIndices(this.directionIndex); - } - else - { - this.quad = i; - } - } - }; - } - public interface BufferFiller - { - /** If true: more data needs to be filled */ - boolean fill(GLVertexBuffer vbo); - - } - - public BufferFiller makeOpaqueBufferFiller(EDhApiGpuUploadMethod method) - { - return new BufferFiller() - { - int dir = 0; - int quad = 0; - - public boolean fill(GLVertexBuffer vbo) - { - if (dir >= 6) - { - vbo.setVertexCount(0); - return false; - } - - int numOfQuads = _countRemainingQuads(); - if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER) - numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER; - if (numOfQuads == 0) - { - vbo.setVertexCount(0); - return false; - } - ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method, - ColumnRenderBuffer.FULL_SIZED_BUFFER); - if (bb == null) - throw new NullPointerException("mapBuffer returned null"); - bb.clear(); - bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE); - while (bb.hasRemaining() && dir < 6) - { - writeData(bb); - } - bb.rewind(); - vbo.unmapBuffer(); - vbo.setVertexCount(numOfQuads * 4); - return dir < 6; - } - - private int _countRemainingQuads() - { - int a = opaqueQuads[dir].size() - quad; - for (int i = dir + 1; i < opaqueQuads.length; i++) - { - a += opaqueQuads[i].size(); - } - return a; - } - - private void writeData(ByteBuffer bb) - { - int startQ = quad; - - int i = startQ; - for (i = startQ; i < opaqueQuads[dir].size(); i++) - { - if (!bb.hasRemaining()) - { - break; - } - putQuad(bb, opaqueQuads[dir].get(i)); - } - - if (i >= opaqueQuads[dir].size()) - { - quad = 0; - dir++; - while (dir < 6 && opaqueQuads[dir].isEmpty()) - { - dir++; - } - } - else - { - quad = i; - } - } - }; - } - - public BufferFiller makeTransparentBufferFiller(EDhApiGpuUploadMethod method) - { - return new BufferFiller() - { - int dir = 0; - int quad = 0; - - public boolean fill(GLVertexBuffer vbo) - { - if (dir >= 6) - { - vbo.setVertexCount(0); - return false; - } - - int numOfQuads = _countRemainingQuads(); - if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER) - numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER; - if (numOfQuads == 0) - { - vbo.setVertexCount(0); - return false; - } - ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method, - ColumnRenderBuffer.FULL_SIZED_BUFFER); - if (bb == null) - throw new NullPointerException("mapBuffer returned null"); - bb.clear(); - bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE); - while (bb.hasRemaining() && dir < 6) - { - writeData(bb); - } - bb.rewind(); - vbo.unmapBuffer(); - vbo.setVertexCount(numOfQuads * 4); - return dir < 6; - } - - private int _countRemainingQuads() - { - int a = transparentQuads[dir].size() - quad; - for (int i = dir + 1; i < transparentQuads.length; i++) - { - a += transparentQuads[i].size(); - } - return a; - } - - private void writeData(ByteBuffer bb) - { - int startQ = quad; - - int i = startQ; - for (i = startQ; i < transparentQuads[dir].size(); i++) - { - if (!bb.hasRemaining()) - { - break; - } - putQuad(bb, transparentQuads[dir].get(i)); - } - - if (i >= transparentQuads[dir].size()) - { - quad = 0; - dir++; - while (dir < 6 && transparentQuads[dir].isEmpty()) - { - dir++; - } - } - else - { - quad = i; - } - } - }; - } - - - //=========// // getters // //=========// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/GLProxy.java b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/GLProxy.java index 4bbfbd6e8..c5556bd9d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/GLProxy.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/GLProxy.java @@ -193,17 +193,7 @@ public class GLProxy return instance; } - public EDhApiGpuUploadMethod getGpuUploadMethod() - { - EDhApiGpuUploadMethod method = Config.Client.Advanced.GpuBuffers.gpuUploadMethod.get(); - if (!this.bufferStorageSupported && method == EDhApiGpuUploadMethod.BUFFER_STORAGE) - { - // if buffer storage isn't supported - // default to DATA since that is the most compatible - method = EDhApiGpuUploadMethod.DATA; - } - return method == EDhApiGpuUploadMethod.AUTO ? this.preferredUploadMethod : method; - } + public EDhApiGpuUploadMethod getGpuUploadMethod() { return this.preferredUploadMethod; } public boolean runningOnRenderThread() { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLBuffer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLBuffer.java index bdfee1f4b..c90ebc1c0 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLBuffer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLBuffer.java @@ -238,7 +238,7 @@ public class GLBuffer implements AutoCloseable // buffer mapping // //================// - public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpensionSize, int bufferHint, int mapFlags) + public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpansionSize, int bufferHint, int mapFlags) { LodUtil.assertTrue(targetSize != 0, "MapBuffer targetSize is 0"); LodUtil.assertTrue(uploadMethod.useEarlyMapping, "Upload method must be one that use early mappings in order to call mapBuffer"); @@ -252,7 +252,7 @@ public class GLBuffer implements AutoCloseable if (this.size < targetSize || this.size > targetSize * BUFFER_SHRINK_TRIGGER) { int newSize = (int) (targetSize * BUFFER_EXPANSION_MULTIPLIER); - if (newSize > maxExpensionSize) newSize = maxExpensionSize; + if (newSize > maxExpansionSize) newSize = maxExpansionSize; this.size = newSize; if (this.bufferStorage) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLVertexBuffer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLVertexBuffer.java index f3957ef56..5c5586e61 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLVertexBuffer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLVertexBuffer.java @@ -77,9 +77,9 @@ public class GLVertexBuffer extends GLBuffer this.vertexCount = vertCount; } - public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpensionSize) + public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpansionSize) { - return super.mapBuffer(targetSize, uploadMethod, maxExpensionSize, + return super.mapBuffer(targetSize, uploadMethod, maxExpansionSize, uploadMethod.useBufferStorage ? GL32.GL_MAP_WRITE_BIT : uploadMethod.useEarlyMapping ? GL32.GL_DYNAMIC_DRAW : GL32.GL_STATIC_DRAW, GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT); From 2c976c9fb1796add51191843d1384fdc69417ee4 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Wed, 7 Aug 2024 07:47:02 -0500 Subject: [PATCH 22/27] Improve fast chunk hash to respect surface block heights --- .../core/wrapperInterfaces/chunk/IChunkWrapper.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java index 9752a2d0b..b059ed445 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java @@ -280,6 +280,7 @@ public interface IChunkWrapper extends IBindable int hash = 31; int primeBlockMultiplier = 227; int primeBiomeMultiplier = 701; + int primeHeightMultiplier = 137; int minBuildHeight = this.getMaxNonEmptyHeight(); int maxBuildHeight = this.getMinNonEmptyHeight(); @@ -294,6 +295,7 @@ public interface IChunkWrapper extends IBindable { hash = (hash * primeBlockMultiplier) + this.getBlockState(x, y, z).hashCode(); hash = (hash * primeBiomeMultiplier) + this.getBiome(x, y, z).hashCode(); + hash = (hash * primeHeightMultiplier) + y; } } } @@ -306,12 +308,14 @@ public interface IChunkWrapper extends IBindable int lightBlockingY = this.getLightBlockingHeightMapValue(x, z); hash = (hash * primeBlockMultiplier) + this.getBlockState(x, lightBlockingY, z).hashCode(); hash = (hash * primeBiomeMultiplier) + this.getBiome(x, lightBlockingY, z).hashCode(); + hash = (hash * primeHeightMultiplier) + lightBlockingY; int solidY = this.getSolidHeightMapValue(x, z); if (solidY != lightBlockingY) { hash = (hash * primeBlockMultiplier) + this.getBlockState(x, solidY, z).hashCode(); hash = (hash * primeBiomeMultiplier) + this.getBiome(x, solidY, z).hashCode(); + hash = (hash * primeHeightMultiplier) + solidY; } } } From 9449433fe8c0c5e323c46920a96feb5cd43ab88c Mon Sep 17 00:00:00 2001 From: James Seibel Date: Wed, 7 Aug 2024 07:47:11 -0500 Subject: [PATCH 23/27] Fix deleting beacons --- .../renderer/generic/BeaconRenderHandler.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java index d1beb0701..abb909a82 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java @@ -143,7 +143,7 @@ public class BeaconRenderHandler { // beam no longer exists at position, remove from DB this.beaconBeamRepo.deleteWithKey(beaconPos); // TODO broken when updating adjacent chunks - this.stopRenderingBeaconAtPos(beaconPos); + this.stopRenderingBeaconAtPos(beaconPos, true); } } @@ -168,7 +168,7 @@ public class BeaconRenderHandler for (int i = 0; i < existingBeamList.size(); i++) { BeaconBeamDTO beam = existingBeamList.get(i); - this.stopRenderingBeaconAtPos(beam.pos); + this.stopRenderingBeaconAtPos(beam.pos, false); } } @@ -199,18 +199,25 @@ public class BeaconRenderHandler }); } - private void stopRenderingBeaconAtPos(DhBlockPos beaconPos) + private void stopRenderingBeaconAtPos(DhBlockPos beaconPos, boolean ignoreReferenceCount) { this.beaconRefCountByBlockPos.compute(beaconPos, (pos, beaconRefCount) -> { - if (beaconRefCount != null - && beaconRefCount.decrementAndGet() <= 0) + // ignoring the reference count is needed when deleting beacons + if (ignoreReferenceCount + || + // respecting the reference count is used when unloading beacons + ( + beaconRefCount != null + && beaconRefCount.decrementAndGet() <= 0 + )) { this.beaconBoxGroup.removeIf((box) -> box.minPos.x == beaconPos.x - && box.minPos.y == beaconPos.y+1 // plus 1 because the beam starts above the beacon - && box.minPos.z == beaconPos.z + && box.minPos.y == beaconPos.y+1 // plus 1 because the beam starts above the beacon + && box.minPos.z == beaconPos.z ); + this.beaconBoxGroup.triggerBoxChange(); return null; } From 0d6ec3d8362a11f189798142726eb250f09b132f Mon Sep 17 00:00:00 2001 From: James Seibel Date: Wed, 7 Aug 2024 18:55:02 -0500 Subject: [PATCH 24/27] Fix frustum culling when the screen is warped --- .../seibel/distanthorizons/core/render/DhFrustumBounds.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/DhFrustumBounds.java b/core/src/main/java/com/seibel/distanthorizons/core/render/DhFrustumBounds.java index 0ef0b5f56..a4ca46ca2 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/DhFrustumBounds.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/DhFrustumBounds.java @@ -54,10 +54,6 @@ public class DhFrustumBounds implements IDhApiCullingFrustum Vector3f lodMin = new Vector3f(lodBlockPosMinX, this.worldMinY, lodBlockPosMinZ); Vector3f lodMax = new Vector3f(lodBlockPosMinX + lodBlockWidth, this.worldMaxY, lodBlockPosMinZ + lodBlockWidth); - if (lodMax.x < this.boundsMin.x || lodMin.x > this.boundsMax.x) return false; - if (lodMax.z < this.boundsMin.z || lodMin.z > this.boundsMax.z) return false; - if (this.worldMaxY < this.boundsMin.y || this.worldMinY > this.boundsMax.y) return false; - return this.frustum.testAab(lodMin, lodMax); } From 7c11bb42588097ee44f63f55f7d94b881bdf7a32 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Fri, 9 Aug 2024 07:25:24 -0500 Subject: [PATCH 25/27] Fix beacons not enabling/disabling correctly --- .../core/generation/DhLightingEngine.java | 2 +- .../core/render/LodQuadTree.java | 33 +++------- .../core/render/LodRenderSection.java | 34 +++++++++- .../core/render/RenderBufferHandler.java | 2 +- .../renderer/generic/BeaconRenderHandler.java | 66 +++++++------------ 5 files changed, 65 insertions(+), 72 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java index c160ef482..901aa8583 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java @@ -147,7 +147,7 @@ public class DhLightingEngine { for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++) { - // set each pos' sky light all the way down until a opaque block is hit + // set each pos' sky light all the way down until an opaque block is hit for (int y = maxY; y >= minY; y--) { IBlockStateWrapper block = chunk.getBlockState(relX, y, relZ); 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 0ac323dab..312ee0a60 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 @@ -189,7 +189,7 @@ public class LodQuadTree extends QuadTree implements IDebugRen try { LodRenderSection renderSection = this.getValue(pos); - if (renderSection != null && renderSection.renderingEnabled) + if (renderSection != null && renderSection.getRenderingEnabled()) { renderSection.uploadRenderDataToGpuAsync(); } @@ -296,7 +296,7 @@ public class LodQuadTree extends QuadTree implements IDebugRen if (DhSectionPos.getDetailLevel(sectionPos) > expectedDetailLevel) { // section detail level too high // - boolean thisPosIsRendering = renderSection.renderingEnabled; + boolean thisPosIsRendering = renderSection.getRenderingEnabled(); boolean allChildrenSectionsAreLoaded = true; // recursively update all child render sections @@ -314,24 +314,8 @@ public class LodQuadTree extends QuadTree implements IDebugRen } else { - if (renderSection.renderingEnabled - && Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get()) - { - // show that this position has just been disabled - DebugRenderer.makeParticle( - new DebugRenderer.BoxParticle( - new DebugRenderer.Box(renderSection.pos, 128f, 156f, 0.09f, Color.CYAN.darker()), - 0.2, 32f - ) - ); - } - // all child positions are loaded, disable this section and enable its children. - if (renderSection.renderingEnabled) - { - this.level.unloadBeaconBeamsInPos(renderSection.pos); - } - renderSection.renderingEnabled = false; + renderSection.setRenderingEnabled(false); // walk back down the tree and enable the child sections //TODO there are probably more efficient ways of doing this, but this will work for now for (int i = 0; i < 4; i++) @@ -382,17 +366,14 @@ public class LodQuadTree extends QuadTree implements IDebugRen if (!parentSectionIsRendering && renderSection.canRender()) { // if rendering is already enabled we don't have to re-enable it - if (!renderSection.renderingEnabled) + if (!renderSection.getRenderingEnabled()) { - renderSection.renderingEnabled = true; - this.level.loadBeaconBeamsInPos(renderSection.pos); - // delete/disable children, all of them will be a lower detail level than requested quadNode.deleteAllChildren((childRenderSection) -> { if (childRenderSection != null) { - if (childRenderSection.renderingEnabled) + if (childRenderSection.getRenderingEnabled()) { // show that this position's rendering has been disabled due to a parent rendering DebugRenderer.makeParticle( @@ -403,10 +384,12 @@ public class LodQuadTree extends QuadTree implements IDebugRen ); } - childRenderSection.renderingEnabled = false; + childRenderSection.setRenderingEnabled(false); childRenderSection.close(); } }); + + renderSection.setRenderingEnabled(true); } } 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 ec71b3419..ff571bfad 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 @@ -70,7 +70,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable private final LodQuadTree quadTree; - public boolean renderingEnabled = false; + private boolean renderingEnabled = false; private boolean canRender = false; /** this reference is necessary so we can determine what VBO to render */ @@ -392,6 +392,38 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable public boolean canRender() { return this.canRender; } + public boolean getRenderingEnabled() { return this.renderingEnabled; } + public void setRenderingEnabled(boolean enabled) + { + // some logic should only be run when enabling/disabling + // a section for the first time + boolean stateChanged = (this.renderingEnabled != enabled); + if (stateChanged) + { + if (enabled) + { + this.level.loadBeaconBeamsInPos(this.pos); + } + else + { + this.level.unloadBeaconBeamsInPos(this.pos); + + if (Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get()) + { + // show that this position has just been disabled + DebugRenderer.makeParticle( + new DebugRenderer.BoxParticle( + new DebugRenderer.Box(this.pos, 128f, 156f, 0.09f, Color.CYAN.darker()), + 0.2, 32f + ) + ); + } + } + } + + this.renderingEnabled = enabled; + } + public boolean gpuUploadInProgress() { return this.buildAndUploadRenderDataToGpuFuture != null; } 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 d02a238fb..aa2c2ed22 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 @@ -311,7 +311,7 @@ public class RenderBufferHandler implements AutoCloseable } ColumnRenderBuffer buffer = renderSection.renderBuffer; - if (buffer == null || !renderSection.renderingEnabled) + if (buffer == null || !renderSection.getRenderingEnabled()) { continue; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java index abb909a82..798289ed0 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java @@ -22,7 +22,6 @@ package com.seibel.distanthorizons.core.render.renderer.generic; import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial; import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup; import com.seibel.distanthorizons.api.objects.math.DhApiVec3d; -import com.seibel.distanthorizons.api.objects.math.DhApiVec3f; import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox; import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading; import com.seibel.distanthorizons.core.config.Config; @@ -42,7 +41,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; public class BeaconRenderHandler { @@ -56,7 +54,7 @@ public class BeaconRenderHandler private final BeaconBeamRepo beaconBeamRepo; private final IDhApiRenderableBoxGroup beaconBoxGroup; - private final HashMap beaconRefCountByBlockPos = new HashMap<>(); + private final HashSet beaconBlockPosSet = new HashSet<>(); @@ -143,7 +141,7 @@ public class BeaconRenderHandler { // beam no longer exists at position, remove from DB this.beaconBeamRepo.deleteWithKey(beaconPos); // TODO broken when updating adjacent chunks - this.stopRenderingBeaconAtPos(beaconPos, true); + this.stopRenderingBeaconAtPos(beaconPos); } } @@ -168,7 +166,7 @@ public class BeaconRenderHandler for (int i = 0; i < existingBeamList.size(); i++) { BeaconBeamDTO beam = existingBeamList.get(i); - this.stopRenderingBeaconAtPos(beam.pos, false); + this.stopRenderingBeaconAtPos(beam.pos); } } @@ -180,52 +178,32 @@ public class BeaconRenderHandler private void startRenderingBeacon(BeaconBeamDTO beacon) { - this.beaconRefCountByBlockPos.compute(beacon.pos, (beamPos, beaconRefCount) -> + if (this.beaconBlockPosSet.add(beacon.pos)) { - if (beaconRefCount == null) { beaconRefCount = new AtomicInteger(); } - if (beaconRefCount.getAndIncrement() == 0) - { - DhApiRenderableBox beaconBox = new DhApiRenderableBox( - new DhApiVec3d(beacon.pos.x, beacon.pos.y+1, beacon.pos.z), - new DhApiVec3d(beacon.pos.x+1, BEAM_TOP_Y, beacon.pos.z+1), - beacon.color, - EDhApiBlockMaterial.ILLUMINATED - ); - - this.beaconBoxGroup.add(beaconBox); - this.beaconBoxGroup.triggerBoxChange(); - } - return beaconRefCount; - }); + DhApiRenderableBox beaconBox = new DhApiRenderableBox( + new DhApiVec3d(beacon.pos.x, beacon.pos.y+1, beacon.pos.z), + new DhApiVec3d(beacon.pos.x+1, BEAM_TOP_Y, beacon.pos.z+1), + beacon.color, + EDhApiBlockMaterial.ILLUMINATED + ); + + this.beaconBoxGroup.add(beaconBox); + this.beaconBoxGroup.triggerBoxChange(); + } } - private void stopRenderingBeaconAtPos(DhBlockPos beaconPos, boolean ignoreReferenceCount) + private void stopRenderingBeaconAtPos(DhBlockPos beaconPos) { - this.beaconRefCountByBlockPos.compute(beaconPos, (pos, beaconRefCount) -> + if (this.beaconBlockPosSet.remove(beaconPos)) { - // ignoring the reference count is needed when deleting beacons - if (ignoreReferenceCount - || - // respecting the reference count is used when unloading beacons - ( - beaconRefCount != null - && beaconRefCount.decrementAndGet() <= 0 - )) + this.beaconBoxGroup.removeIf((box) -> { - this.beaconBoxGroup.removeIf((box) -> - box.minPos.x == beaconPos.x + return box.minPos.x == beaconPos.x && box.minPos.y == beaconPos.y+1 // plus 1 because the beam starts above the beacon - && box.minPos.z == beaconPos.z - ); - - this.beaconBoxGroup.triggerBoxChange(); - return null; - } - else - { - return beaconRefCount; - } - }); + && box.minPos.z == beaconPos.z; + }); + this.beaconBoxGroup.triggerBoxChange(); + } } private void updateBeaconColor(BeaconBeamDTO newBeam) From 8d78a1ad742bef69db57e7fb52227c596ff11af4 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Fri, 9 Aug 2024 07:25:49 -0500 Subject: [PATCH 26/27] Rename BeaconBeamDTO.pos -> blockPos --- .../renderer/generic/BeaconRenderHandler.java | 18 +++++++++--------- .../core/sql/dto/BeaconBeamDTO.java | 9 ++++----- .../core/sql/repo/BeaconBeamRepo.java | 12 ++++++------ 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java index 798289ed0..71b991155 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java @@ -94,8 +94,8 @@ public class BeaconRenderHandler for (int i = 0; i < newBeamList.size(); i++) { BeaconBeamDTO beam = newBeamList.get(i); - newBeamByPos.put(beam.pos, beam); - allPosSet.add(beam.pos); + newBeamByPos.put(beam.blockPos, beam); + allPosSet.add(beam.blockPos); } // get existing beams @@ -104,8 +104,8 @@ public class BeaconRenderHandler for (int i = 0; i < existingBeamList.size(); i++) { BeaconBeamDTO beam = existingBeamList.get(i); - existingBeamByPos.put(beam.pos, beam); - allPosSet.add(beam.pos); + existingBeamByPos.put(beam.blockPos, beam); + allPosSet.add(beam.blockPos); } @@ -166,7 +166,7 @@ public class BeaconRenderHandler for (int i = 0; i < existingBeamList.size(); i++) { BeaconBeamDTO beam = existingBeamList.get(i); - this.stopRenderingBeaconAtPos(beam.pos); + this.stopRenderingBeaconAtPos(beam.blockPos); } } @@ -178,11 +178,11 @@ public class BeaconRenderHandler private void startRenderingBeacon(BeaconBeamDTO beacon) { - if (this.beaconBlockPosSet.add(beacon.pos)) + if (this.beaconBlockPosSet.add(beacon.blockPos)) { DhApiRenderableBox beaconBox = new DhApiRenderableBox( - new DhApiVec3d(beacon.pos.x, beacon.pos.y+1, beacon.pos.z), - new DhApiVec3d(beacon.pos.x+1, BEAM_TOP_Y, beacon.pos.z+1), + new DhApiVec3d(beacon.blockPos.x, beacon.blockPos.y+1, beacon.blockPos.z), + new DhApiVec3d(beacon.blockPos.x+1, BEAM_TOP_Y, beacon.blockPos.z+1), beacon.color, EDhApiBlockMaterial.ILLUMINATED ); @@ -208,7 +208,7 @@ public class BeaconRenderHandler private void updateBeaconColor(BeaconBeamDTO newBeam) { - DhBlockPos pos = newBeam.pos; + DhBlockPos pos = newBeam.blockPos; for (int i = 0; i < this.beaconBoxGroup.size(); i++) { DhApiRenderableBox box = this.beaconBoxGroup.get(i); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/BeaconBeamDTO.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/BeaconBeamDTO.java index 452af3873..96fc63549 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/BeaconBeamDTO.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/BeaconBeamDTO.java @@ -21,14 +21,13 @@ package com.seibel.distanthorizons.core.sql.dto; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.pos.DhBlockPos; -import com.seibel.distanthorizons.core.pos.DhBlockPos; import java.awt.*; /** handles storing {@link FullDataSourceV2}'s in the database. */ public class BeaconBeamDTO implements IBaseDTO { - public DhBlockPos pos; + public DhBlockPos blockPos; public Color color; @@ -37,9 +36,9 @@ public class BeaconBeamDTO implements IBaseDTO // constructor // //=============// - public BeaconBeamDTO(DhBlockPos pos, Color color) + public BeaconBeamDTO(DhBlockPos blockPos, Color color) { - this.pos = pos; + this.blockPos = blockPos; this.color = color; } @@ -50,6 +49,6 @@ public class BeaconBeamDTO implements IBaseDTO //===========// @Override - public DhBlockPos getKey() { return this.pos; } + public DhBlockPos getKey() { return this.blockPos; } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/BeaconBeamRepo.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/BeaconBeamRepo.java index 9616a79b2..5651ae932 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/BeaconBeamRepo.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/BeaconBeamRepo.java @@ -100,9 +100,9 @@ public class BeaconBeamRepo extends AbstractDhRepo PreparedStatement statement = this.createPreparedStatement(sql); int i = 1; - statement.setObject(i++, dto.pos.x); - statement.setObject(i++, dto.pos.y); - statement.setObject(i++, dto.pos.z); + statement.setObject(i++, dto.blockPos.x); + statement.setObject(i++, dto.blockPos.y); + statement.setObject(i++, dto.blockPos.z); statement.setObject(i++, dto.color.getRed()); statement.setObject(i++, dto.color.getGreen()); @@ -132,9 +132,9 @@ public class BeaconBeamRepo extends AbstractDhRepo statement.setObject(i++, System.currentTimeMillis()); // last modified unix time - statement.setObject(i++, dto.pos.x); - statement.setObject(i++, dto.pos.y); - statement.setObject(i++, dto.pos.z); + statement.setObject(i++, dto.blockPos.x); + statement.setObject(i++, dto.blockPos.y); + statement.setObject(i++, dto.blockPos.z); return statement; } From dac51a9eea0b2d78b8c320996ec9b40fb307370d Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 11 Aug 2024 09:54:54 -0500 Subject: [PATCH 27/27] Add mod compat warning message config --- .../core/api/internal/ClientApi.java | 26 +++++++++++-------- .../distanthorizons/core/config/Config.java | 7 +++++ .../assets/distanthorizons/lang/en_us.json | 2 ++ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java index 355396630..691d50870 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java @@ -497,7 +497,7 @@ public class ClientApi { // logging // - this.sendChatMessagesNow(); + this.sendQueuedChatMessages(); IProfilerWrapper profiler = MC.getProfiler(); profiler.pop(); // get out of "terrain" @@ -660,7 +660,7 @@ public class ClientApi } } - private void sendChatMessagesNow() + private void sendQueuedChatMessages() { // dev build if (ModInfo.IS_DEV_BUILD && !this.configOverrideReminderPrinted && MC.playerExists()) @@ -668,10 +668,12 @@ public class ClientApi this.configOverrideReminderPrinted = true; // remind the user that this is a development build - MC.sendChatMessage("\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r"); - MC.sendChatMessage("Issues may occur with this version."); - MC.sendChatMessage("Here be dragons!"); - MC.sendChatMessage(""); + String message = + // green text + "\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r\n" + + "Issues may occur with this version.\n" + + "Here be dragons!\n"; + MC.sendChatMessage(message); } // memory @@ -686,11 +688,13 @@ public class ClientApi long maxMemoryInBytes = Runtime.getRuntime().maxMemory(); if (maxMemoryInBytes < minimumRecommendedMemoryInBytes) { - MC.sendChatMessage("\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r"); - MC.sendChatMessage("Stuttering or low FPS may occur."); - MC.sendChatMessage("Please increase Minecraft's available memory to 4 gigabytes."); - MC.sendChatMessage("This warning can be disabled in DH's config under Advanced -> Logging."); - MC.sendChatMessage(""); + String message = + // orange text + "\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r \n" + + "Stuttering or low FPS may occur. \n" + + "Please increase Minecraft's available memory to 4 gigabytes. \n" + + "This warning can be disabled in DH's config under Advanced -> Logging. \n"; + MC.sendChatMessage(message); } } 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 463ebcbea..1479640cb 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 @@ -1179,6 +1179,13 @@ public class Config + "giving some basic information about how DH will function.") .build(); + public static ConfigEntry showModCompatibilityWarningsOnStartup = new ConfigEntry.Builder() + .set(true) + .comment("" + + "If enabled, a chat message will be displayed when a potentially problematic \n" + + "mod is installed alongside DH.") + .build(); + } public static class Debugging 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 6399f811a..010226db5 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -566,6 +566,8 @@ "Show Low Memory Warning", "distanthorizons.config.client.advanced.logging.showReplayWarningOnStartup": "Show Replay Warning", + "distanthorizons.config.client.advanced.logging.showModCompatibilityWarningsOnStartup": + "Show Mod Compatibility Warnings",