diff --git a/api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiBlockColorOverrideEvent.java b/api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiBlockColorOverrideEvent.java index 86e95094a..6fc5f3eaa 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiBlockColorOverrideEvent.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiBlockColorOverrideEvent.java @@ -111,7 +111,7 @@ public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent implements IDependencyInjector // Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it { - protected final Map, ArrayList> dependencies = new HashMap<>(); + protected final HashMap, ArrayList> dependencies = new HashMap<>(); /** Internal class reference to BindableType since we can't get it any other way. */ protected final Class bindableInterface; diff --git a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java index ee716fa87..d83b1b907 100644 --- a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java +++ b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java @@ -43,7 +43,7 @@ public final class ModInfo public static final String NAME = "DistantHorizons"; /** Human-readable version of NAME */ public static final String READABLE_NAME = "Distant Horizons"; - public static final String VERSION = "3.0.3-b-dev"; + public static final String VERSION = "3.0.4-b-dev"; /** Returns true if the current build is an unstable developer build, false otherwise. */ public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev"); @@ -52,7 +52,7 @@ public final class ModInfo /** This version should be updated whenever new methods are added to the DH API */ public static final int API_MINOR_VERSION = 1; /** This version should be updated whenever non-breaking fixes are added to the DH API */ - public static final int API_PATCH_VERSION = 0; + public static final int API_PATCH_VERSION = 1; /** If the config file has an older version it'll be re-created from scratch. */ public static final int CONFIG_FILE_VERSION = 4; 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 33fe77abd..a63fce45b 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 @@ -42,6 +42,7 @@ import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IImmersivePortalsAccessor; +import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhMetaRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhVanillaFadeRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTestTriangleRenderer; @@ -64,6 +65,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; +import java.awt.*; import java.io.File; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -158,6 +160,15 @@ public class ClientApi public static long firstRenderTimeMs = 0; + /** + * keeping track of this is necessary to fix + * out-of-date LODs from rendering when the shading + * is changed by Iris, causing LODs to often + * lack the side shading, which looks pretty bad + * when shaders are disabled. + */ + private boolean irisShadersEnabledLastFrame = false; + //==============// @@ -466,6 +477,27 @@ public class ClientApi } //endregion + + + + //====================// + // Iris data re-build // + //====================// + //region + + // delayed getter since ClientApi is created before this accessor is bound + IIrisAccessor irisAccessor = ModAccessorInjector.INSTANCE.get(IIrisAccessor.class); + if (irisAccessor != null) + { + boolean shadersActive = irisAccessor.isShaderPackInUse(); + if (this.irisShadersEnabledLastFrame != shadersActive) + { + this.irisShadersEnabledLastFrame = shadersActive; + DhApi.Delayed.renderProxy.clearRenderDataCache(); + } + } + + //endregion } } 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 038433e84..790d6c2cf 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 @@ -964,6 +964,7 @@ public class Config public static ConfigCategory debugWireframe = new ConfigCategory.Builder().set(DebugWireframe.class).build(); public static ConfigCategory openGl = new ConfigCategory.Builder().set(OpenGl.class).build(); public static ConfigCategory columnBuilderDebugging = new ConfigCategory.Builder().set(ColumnBuilderDebugging.class).build(); + public static ConfigCategory positionFinderDebugging = new ConfigCategory.Builder().set(PositionFinder.class).build(); public static ConfigCategory f3Screen = new ConfigCategory.Builder().set(F3Screen.class).build(); public static ConfigCategory exampleConfigScreen = new ConfigCategory.Builder().set(ExampleConfigScreen.class).build(); @@ -1095,6 +1096,36 @@ public class Config } + public static class PositionFinder + { + //public static ConfigUIComment positionFinderHeader = new ConfigUIComment.Builder().setParentConfigClass(ColumnBuilderDebugging.class).build(); + + public static ConfigEntry positionFinderEnable = new ConfigEntry.Builder() + .set(false) + .build(); + + public static ConfigEntry positionFinderDetailLevel = new ConfigEntry.Builder() + .set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) + .build(); + public static ConfigEntry positionFinderXPos = new ConfigEntry.Builder() + .set(0) + .build(); + public static ConfigEntry positionFinderZPos = new ConfigEntry.Builder() + .set(0) + .build(); + + public static ConfigEntry positionFinderMinBlockY = new ConfigEntry.Builder() + .set(-64) + .build(); + public static ConfigEntry positionFinderMaxBlockY = new ConfigEntry.Builder() + .set(125) + .build(); + public static ConfigEntry positionFinderMarginPercent = new ConfigEntry.Builder() + .set(0.0f) + .build(); + + } + public static class F3Screen { public static ConfigUIComment f3ScreenHeader = new ConfigUIComment.Builder().setParentConfigClass(F3Screen.class).build(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/BufferQuad.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/BufferQuad.java index e090e1fb8..4faf71b36 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/BufferQuad.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/BufferQuad.java @@ -59,9 +59,19 @@ public final class BufferQuad public boolean hasError = false; + // Pre-computed sort keys to avoid recomputing on every comparison + // Slight increase in memory for reduction in cpu usage + public final long sortKeyEastWest; + public final long sortKeyNorthSouth; - BufferQuad( + + //=============// + // constructor // + //=============// + //region + + public BufferQuad( short x, short y, short z, short widthEastWest, short widthNorthSouthOrHeight, int color, byte irisBlockMaterialId, byte skylight, byte blockLight, EDhDirection direction) @@ -85,64 +95,46 @@ public final class BufferQuad this.skyLight = skylight; this.blockLight = blockLight; this.direction = direction; + this.sortKeyEastWest = computeSortKey(direction, true); + this.sortKeyNorthSouth = computeSortKey(direction, false); } - - - - /** a rough but fast calculation */ - double calculateDistance(double relativeX, double relativeY, double relativeZ) + private long computeSortKey(EDhDirection dir, boolean eastWest) { - return Math.pow(relativeX - this.x, 2) + Math.pow(relativeY - this.y, 2) + Math.pow(relativeZ - this.z, 2); + if (eastWest) + { + switch (dir.axis) + { + case X: return (long) x << 48 | (long) y << 32 | (long) z << 16; + case Y: return (long) y << 48 | (long) z << 32 | (long) x << 16; + case Z: return (long) z << 48 | (long) y << 32 | (long) x << 16; + default: throw new IllegalArgumentException("Invalid Axis enum: [" + dir.axis + "]."); + } + } + else + { + switch (dir.axis) + { + case X: return (long) x << 48 | (long) z << 32 | (long) y << 16; + case Y: return (long) y << 48 | (long) x << 32 | (long) z << 16; + case Z: return (long) z << 48 | (long) x << 32 | (long) y << 16; + default: throw new IllegalArgumentException("Invalid Axis enum: [" + dir.axis + "]."); + } + } } - /** compares this quad's position to the given quad */ + //endregion + + + + /** compares this quad's position to the given quad using pre-computed sort keys */ public int compare(BufferQuad quad, BufferMergeDirectionEnum compareDirection) { if (this.direction != quad.direction) throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + this.direction); - if (compareDirection == BufferMergeDirectionEnum.EastWest) - { - switch (this.direction.axis) - { - case X: - return threeDimensionalCompare(this.x, this.y, this.z, quad.x, quad.y, quad.z); - case Y: - return threeDimensionalCompare(this.y, this.z, this.x, quad.y, quad.z, quad.x); - case Z: - return threeDimensionalCompare(this.z, this.y, this.x, quad.z, quad.y, quad.x); - - default: - throw new IllegalArgumentException("Invalid Axis enum: [" + this.direction.axis + "]."); - } - } - else - { - switch (this.direction.axis) - { - case X: - return threeDimensionalCompare(this.x, this.z, this.y, quad.x, quad.z, quad.y); - case Y: - return threeDimensionalCompare(this.y, this.x, this.z, quad.y, quad.x, quad.z); - case Z: - return threeDimensionalCompare(this.z, this.x, this.y, quad.z, quad.x, quad.y); - - default: - throw new IllegalArgumentException("Invalid Axis enum: [" + this.direction.axis + "]."); - } - } - } - /** - * Compares two 3D points A and B.
- * The X, Y, and Z coordinates can be passed into parameters 0, 1, and 2 in any order - * provided they are in the same order for both A and B.
- * With the 0th parameter being the most significant when comparing. - */ - private static int threeDimensionalCompare(short a0, short a1, short a2, short b0, short b1, short b2) - { - long a = (long) a0 << 48 | (long) a1 << 32 | (long) a2 << 16; - long b = (long) b0 << 48 | (long) b1 << 32 | (long) b2 << 16; - return Long.compare(a, b); + return compareDirection == BufferMergeDirectionEnum.EastWest + ? Long.compare(this.sortKeyEastWest, quad.sortKeyEastWest) + : Long.compare(this.sortKeyNorthSouth, quad.sortKeyNorthSouth); } @@ -154,11 +146,15 @@ public final class BufferQuad public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection) { if (quad.hasError || this.hasError) + { return false; + } // only merge quads that are in the same direction if (this.direction != quad.direction) + { return false; + } // make sure these quads share the same perpendicular axis if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y) @@ -175,7 +171,6 @@ public final class BufferQuad short otherParallelCompareStartPos; switch (this.direction.axis) { - default: // shouldn't normally happen, just here to make the compiler happy case X: if (mergeDirection == BufferMergeDirectionEnum.EastWest) { @@ -232,6 +227,9 @@ public final class BufferQuad otherParallelCompareStartPos = quad.z; } break; + + default: // shouldn't normally happen, just here to make the compiler happy + throw new IllegalArgumentException("Unsupported axis: ["+this.direction.axis+"]"); } // get the width of this quad in the relevant axis @@ -333,4 +331,6 @@ public final class BufferQuad return true; } + + } 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 cfa0082e2..bff8ece2a 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 @@ -341,13 +341,24 @@ public class ColumnBox // Apply light to the range [adjMinY, adjMaxY) - applyLightToRange(segments, newSegments, adjMinY, adjMaxY, lightToApply); + applyLightToRangeAndPopulateNewSgements(segments, newSegments, adjMinY, adjMaxY, lightToApply); + { + // swap references so we can use the newly populated segments + LongArrayList temp = segments; + segments = newSegments; + newSegments = temp; + } // Fill overhang area [adjMaxY, adjAboveMinY) with adjSkyLight short adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint); if (adjMaxY < adjAboveMinY) { - applyLightToRange(segments, newSegments, adjMaxY, adjAboveMinY, adjSkyLight); + applyLightToRangeAndPopulateNewSgements(segments, newSegments, adjMaxY, adjAboveMinY, adjSkyLight); + { + LongArrayList temp = segments; + segments = newSegments; + newSegments = temp; + } } } @@ -373,10 +384,11 @@ public class ColumnBox /** * Apply the new light value over the given y range, * splitting segments as needed + * and putting the new segments into "newSegments" *

* source: claude.ai */ - private static void applyLightToRange( + private static void applyLightToRangeAndPopulateNewSgements( LongArrayList segments, LongArrayList newSegments, short rangeStart, short rangeEnd, byte newLight) @@ -419,9 +431,6 @@ public class ColumnBox newSegments.add(YSegmentUtil.encode(rangeEnd, endY, skyLight)); } } - - segments.clear(); - segments.addAll(newSegments); } private static void tryAddVerticalFaceWithSkyLightToBuilder( 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 3f4fe08a2..b73573c7c 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 @@ -55,26 +55,6 @@ public class ColumnRenderBufferBuilder // vbo building // //==============// - /** @link adjData should be null for adjacent sections that cross detail level boundaries */ - public static CompletableFuture uploadBuffersAsync( - IDhClientLevel clientLevel, - long pos, - LodQuadBuilder quadBuilder - ) - { - DhBlockPos minBlockPos = new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getLevelWrapper().getMinHeight(), DhSectionPos.getMinCornerBlockZ(pos)); - LodBufferContainer bufferContainer = new LodBufferContainer(pos, minBlockPos); - CompletableFuture uploadFuture = bufferContainer.tryMakeAndUploadBuffersAsync(quadBuilder); - uploadFuture.whenComplete((uploadedBuffer, exception) -> - { - // clean up if not uploaded - if (uploadedBuffer != null && !uploadedBuffer.buffersUploaded) - { - uploadedBuffer.close(); - } - }); - return uploadFuture; - } public static void makeLodRenderData( LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel, ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodBufferContainer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodBufferContainer.java index 8f5f2c1cb..04c6f5102 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodBufferContainer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodBufferContainer.java @@ -20,13 +20,13 @@ package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; import com.seibel.distanthorizons.core.util.ExceptionUtil; -import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition; import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper; @@ -37,7 +37,6 @@ import org.lwjgl.system.MemoryUtil; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; /** * Java representation of one or more OpenGL buffers for rendering. @@ -63,8 +62,6 @@ public class LodBufferContainer implements AutoCloseable public ILodContainerUniformBufferWrapper uniformContainer = WRAPPER_FACTORY.createLodContainerUniformWrapper(); - private final AtomicReference> uploadFutureRef = new AtomicReference<>(null); - //==============// @@ -72,7 +69,7 @@ public class LodBufferContainer implements AutoCloseable //==============// //region - public LodBufferContainer(long pos, DhBlockPos minCornerBlockPos) + private LodBufferContainer(long pos, DhBlockPos minCornerBlockPos) { this.pos = pos; this.minCornerBlockPos = minCornerBlockPos; @@ -92,41 +89,12 @@ public class LodBufferContainer implements AutoCloseable //region /** Should be run on a DH thread. */ - public synchronized CompletableFuture tryMakeAndUploadBuffersAsync(LodQuadBuilder builder) + public static CompletableFuture tryMakeAndUploadBuffersAsync( + long pos, IDhClientLevel clientLevel, + LodQuadBuilder builder) { - //================// - // handle futures // - //================// - //region - - // separate variable to prevent race condition when checking null - CompletableFuture oldFuture = this.uploadFutureRef.get(); - if (oldFuture != null) - { - // upload already in process - return oldFuture; - } - // new upload needed CompletableFuture future = new CompletableFuture<>(); - future.handle((lodBufferContainer, throwable) -> - { - if (!this.uploadFutureRef.compareAndSet(future, null)) - { - LOGGER.warn("upload future ref changed for pos ["+DhSectionPos.toString(this.pos)+"]."); - } - - return null; - }); - - if (!this.uploadFutureRef.compareAndSet(null, future)) - { - oldFuture = this.uploadFutureRef.get(); - LodUtil.assertTrue(oldFuture != null, "Concurrency error"); - return oldFuture; - } - - //endregion @@ -135,91 +103,119 @@ public class LodBufferContainer implements AutoCloseable //================// //region + DhBlockPos minCornerBlockPos = new DhBlockPos( + DhSectionPos.getMinCornerBlockX(pos), + clientLevel.getLevelWrapper().getMinHeight(), + DhSectionPos.getMinCornerBlockZ(pos)); + LodBufferContainer bufferContainer = new LodBufferContainer(pos, minCornerBlockPos); + + // create CPU vertex buffers ArrayList opaqueBuffers = builder.makeOpaqueVertexBuffers(); ArrayList transparentBuffers = builder.makeTransparentVertexBuffers(); - this.vboOpaqueWrappers = resizeWrapperArray(this.vboOpaqueWrappers, opaqueBuffers.size()); - this.vboTransparentWrappers = resizeWrapperArray(this.vboTransparentWrappers, transparentBuffers.size()); + // update arrays to contain buffers + bufferContainer.vboOpaqueWrappers = resizeWrapperArray(bufferContainer.vboOpaqueWrappers, opaqueBuffers.size()); + bufferContainer.vboTransparentWrappers = resizeWrapperArray(bufferContainer.vboTransparentWrappers, transparentBuffers.size()); - // mac requires separate IBO objects for each VBO when using OpenGL, + // create CPU index buffers if needed. + // Mac requires separate IBO objects for each VBO when using OpenGL, // all other OS's can share a single IBO for quicker loading times boolean useSingleIbo = RENDER_DEF.useSingleIbo(); - @Nullable ArrayList opaqueIndexBuffers = useSingleIbo ? null : this.createIndexBuffers(opaqueBuffers); - @Nullable ArrayList transparentIndexBuffers = useSingleIbo ? null : this.createIndexBuffers(transparentBuffers); + @Nullable ArrayList opaqueIndexBuffers = useSingleIbo ? null : bufferContainer.createIndexBuffers(opaqueBuffers); + @Nullable ArrayList transparentIndexBuffers = useSingleIbo ? null : bufferContainer.createIndexBuffers(transparentBuffers); //endregion - //================// - // upload buffers // - //================// - //region + //=============// + // create VBOs // + //=============// + //region - try + CompletableFuture createFuture = new CompletableFuture(); + RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Setup", () -> { - //=============// - // create VBOs // - //=============// - - CompletableFuture createOpaqueFuture = createBufferWrappersAsync(future, this.vboOpaqueWrappers, opaqueBuffers); - CompletableFuture createTransparentFuture = createBufferWrappersAsync(future, this.vboTransparentWrappers, transparentBuffers); - - CompletableFuture createFuture = CompletableFuture.allOf(createOpaqueFuture, createTransparentFuture); - createFuture.exceptionally((Throwable e) -> + try { - // create VBOs failed // + // skip this event if requested + if (Thread.interrupted() + || future.isCancelled()) + { + throw new InterruptedException(); + } + createBufferWrappers(bufferContainer.vboOpaqueWrappers, opaqueBuffers); + createBufferWrappers(bufferContainer.vboTransparentWrappers, transparentBuffers); + + createFuture.complete(null); + } + catch (Exception e) + { if (!ExceptionUtil.isShutdownException(e)) { - LOGGER.error("Unexpected issue creating buffer [" + this.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e); + LOGGER.error("Unexpected issue creating buffers for pos: ["+DhSectionPos.toString(bufferContainer.pos)+"], error: ["+e.getMessage()+"].", e); } + + bufferContainer.close(); + createFuture.completeExceptionally(e); + } + }); + + //endregion + + + + //====================// + // upload VBOs to GPU // + //====================// + //region + + createFuture.exceptionally((Throwable e) -> + { + // create VBOs failed // + if (!ExceptionUtil.isShutdownException(e)) + { + LOGGER.error("Unexpected issue creating buffer [" + bufferContainer.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e); + } + + bufferContainer.close(); + future.completeExceptionally(e); + return null; + }); + createFuture.thenRun(() -> + { + CompletableFuture opaqueFuture = uploadBuffersAsync(future, bufferContainer.vboOpaqueWrappers, opaqueBuffers, opaqueIndexBuffers); + CompletableFuture transparentFuture = uploadBuffersAsync(future, bufferContainer.vboTransparentWrappers, transparentBuffers, transparentIndexBuffers); + CompletableFuture uploadFuture = CompletableFuture.allOf(opaqueFuture, transparentFuture); + uploadFuture.exceptionally((Throwable e) -> + { + // upload failed // + if (!ExceptionUtil.isShutdownException(e)) + { + LOGGER.error("Unexpected issue uploading buffer [" + bufferContainer.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e); + } + + bufferContainer.close(); future.completeExceptionally(e); return null; }); - createFuture.thenRun(() -> + uploadFuture.thenRun(() -> { - //=============// - // upload VBOs // - //=============// - - CompletableFuture opaqueFuture = uploadBuffersAsync(future, this.vboOpaqueWrappers, opaqueBuffers, opaqueIndexBuffers); - CompletableFuture transparentFuture = uploadBuffersAsync(future, this.vboTransparentWrappers, transparentBuffers, transparentIndexBuffers); - - CompletableFuture uploadFuture = CompletableFuture.allOf(opaqueFuture, transparentFuture); - uploadFuture.exceptionally((Throwable e) -> - { - // upload failed // - - if (!ExceptionUtil.isShutdownException(e)) - { - LOGGER.error("Unexpected issue uploading buffer [" + this.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e); - } - future.completeExceptionally(e); - return null; - }); - uploadFuture.thenRun(() -> - { - // upload success / - - this.buffersUploaded = true; - future.complete(this); - }); + // upload success // + bufferContainer.buffersUploaded = true; + future.complete(bufferContainer); }); - } - catch (Exception e) - { - if (!ExceptionUtil.isShutdownException(e)) - { - LOGGER.error("Unexpected issue prepping buffer uploading [" + this.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e); - } - future.completeExceptionally(e); - } + }); + + //endregion - //================// - // buffer cleanup // - //================// + + //====================// + // CPU Buffer cleanup // + //====================// + //region future.whenComplete((LodBufferContainer lodBufferContainer, Throwable throwable) -> { @@ -290,11 +286,8 @@ public class LodBufferContainer implements AutoCloseable return newVbos; } - private static CompletableFuture createBufferWrappersAsync( - CompletableFuture parentFuture, - IVertexBufferWrapper[] vboWrappers, ArrayList vertexBuffers) + private static void createBufferWrappers(IVertexBufferWrapper[] vboWrappers, ArrayList vertexBuffers) { - ArrayList> createVboFutureList = new ArrayList<>(); for (int i = 0; i < vertexBuffers.size(); i++) { if (i >= vboWrappers.length) @@ -304,45 +297,9 @@ public class LodBufferContainer implements AutoCloseable if (vboWrappers[i] == null) { - final int finalVboIndex = i; - - CompletableFuture future = new CompletableFuture<>(); - createVboFutureList.add(future); - - RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Setup", () -> - { - try - { - // skip this event if requested - if (Thread.interrupted() - || parentFuture.isCancelled()) - { - throw new InterruptedException(); - } - - - vboWrappers[finalVboIndex] = WRAPPER_FACTORY.createVboWrapper("distantHorizons:McLodRenderer"); - future.complete(null); - } - catch (Exception e) - { - future.completeExceptionally(e); - } - }); + vboWrappers[i] = WRAPPER_FACTORY.createVboWrapper("distantHorizons:McLodRenderer"); } } - - if (createVboFutureList.size() == 0) - { - return CompletableFuture.completedFuture(null); - } - - CompletableFuture[] futureArray = new CompletableFuture[createVboFutureList.size()]; - for (int i = 0; i < createVboFutureList.size(); i++) - { - futureArray[i] = createVboFutureList.get(i); - } - return CompletableFuture.allOf(futureArray); } /** Index buffers should be null if {@link AbstractDhRenderApiDefinition#useSingleIbo()} returns true. */ @@ -365,8 +322,6 @@ public class LodBufferContainer implements AutoCloseable // final variables for use in lambdas // - final int finalVboIndex = vboIndex; - final IVertexBufferWrapper finalVboWrapper = vboWrappers[vboIndex]; final ByteBuffer finalVertexBuffer = vertexBuffers.get(vboIndex); @@ -385,6 +340,8 @@ public class LodBufferContainer implements AutoCloseable CompletableFuture vertexUploadFuture = new CompletableFuture<>(); uploadFutureList.add(vertexUploadFuture); + + final StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer VBO Upload", () -> { try @@ -396,21 +353,12 @@ public class LodBufferContainer implements AutoCloseable throw new InterruptedException(); } - - try - { - finalVboWrapper.uploadVertexBuffer(finalVertexBuffer, finalVertexCount); - vertexUploadFuture.complete(null); - } - catch (Exception e) - { - vboWrappers[finalVboIndex] = null; - finalVboWrapper.close(); - LOGGER.error("Failed to upload buffer. Error: [" + e.getMessage() + "].", e); - } + finalVboWrapper.uploadVertexBuffer(finalVertexBuffer, finalVertexCount); + vertexUploadFuture.complete(null); } catch (Exception e) { + LOGGER.error("Failed to upload buffer. Error: [" + e.getMessage() + "].", e); vertexUploadFuture.completeExceptionally(e); } }); @@ -445,6 +393,7 @@ public class LodBufferContainer implements AutoCloseable } catch (Exception e) { + finalVboWrapper.close(); indexUploadFuture.completeExceptionally(e); } }); @@ -532,26 +481,29 @@ public class LodBufferContainer implements AutoCloseable RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Close", () -> { - for (IVertexBufferWrapper buffer : this.vboOpaqueWrappers) - { - if (buffer != null) - { - buffer.close(); - } - } - - for (IVertexBufferWrapper buffer : this.vboTransparentWrappers) - { - if (buffer != null) - { - buffer.close(); - } - } + tryCloseBufferWrapperArray(this.vboOpaqueWrappers); + tryCloseBufferWrapperArray(this.vboTransparentWrappers); this.uniformContainer.close(); }); } + private static void tryCloseBufferWrapperArray(@Nullable IVertexBufferWrapper[] bufferWrappers) + { + if (bufferWrappers != null) + { + for (int i = 0; i < bufferWrappers.length; i++) + { + IVertexBufferWrapper buffer = bufferWrappers[i]; + bufferWrappers[i] = null; + if (buffer != null) + { + buffer.close(); + } + } + } + } + //endregion 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 f97f67da3..ba7bb74d2 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 @@ -61,7 +61,7 @@ public class LodQuadBuilder public static final int BYTES_PER_QUAD = BYTES_PER_VERTEX * 4; public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][] - ///region + //region { // X,Z // { // UP @@ -109,7 +109,7 @@ public class LodQuadBuilder {0, 0}, // 3 }, }; - ///endregion + //endregion private int premergeCount = 0; 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 3af137531..ef0725889 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 @@ -129,6 +129,7 @@ public class FullDataToRenderDataTransformer ColumnRenderView tempExpandingColumnView = ColumnRenderView.getPooled(); RenderDataPointReducingList reducingList = new RenderDataPointReducingList()) { + DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable(); for (int x = 0; x < FullDataSourceV2.WIDTH; x++) { for (int z = 0; z < FullDataSourceV2.WIDTH; z++) @@ -142,7 +143,7 @@ public class FullDataToRenderDataTransformer baseX + BitShiftUtil.pow(x, dataDetail), baseZ + BitShiftUtil.pow(z, dataDetail), columnArrayView, dataColumn, // pooled references so we don't need to re-allocate/get them 4000 times per render source - phantomCheckout, tempExpandingColumnView, reducingList); + phantomCheckout, tempExpandingColumnView, reducingList, mutableBlockPos); } } } @@ -157,7 +158,7 @@ public class FullDataToRenderDataTransformer ColumnRenderView columnArrayView, LongArrayList fullDataColumn, // pooled references - PhantomArrayListCheckout phantomCheckout, ColumnRenderView tempExpandingColumnView, RenderDataPointReducingList reducingList) + PhantomArrayListCheckout phantomCheckout, ColumnRenderView tempExpandingColumnView, RenderDataPointReducingList reducingList, DhBlockPosMutable mutableBlockPos) { // we can't do anything if the full data is missing or empty if (fullDataColumn == null @@ -170,7 +171,7 @@ public class FullDataToRenderDataTransformer if (fullDataLength <= columnArrayView.maxVerticalSliceCount) { // Directly use the arrayView since it fits. - setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn); + setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn, mutableBlockPos); } else { @@ -178,7 +179,7 @@ public class FullDataToRenderDataTransformer // expand the ColumnArrayView to fit the new larger max vertical size tempExpandingColumnView.populate(dataArrayList, fullDataLength, 0, fullDataLength); - setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, tempExpandingColumnView, fullDataColumn); + setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, tempExpandingColumnView, fullDataColumn, mutableBlockPos); columnArrayView.changeVerticalSizeFrom(tempExpandingColumnView, reducingList); } @@ -186,7 +187,7 @@ public class FullDataToRenderDataTransformer private static void setRenderColumnView( IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource, int blockX, int blockZ, - ColumnRenderView renderColumnData, LongArrayList fullColumnData) + ColumnRenderView renderColumnData, LongArrayList fullColumnData, DhBlockPosMutable mutableBlockPos) { //===============// // config values // @@ -242,7 +243,8 @@ public class FullDataToRenderDataTransformer FullDataPointIdMap fullDataMapping = fullDataSource.mapping; - DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable(blockX, 0, blockZ); + mutableBlockPos.setX(blockX); + mutableBlockPos.setZ(blockZ); // goes from the top down for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++) @@ -450,8 +452,17 @@ public class FullDataToRenderDataTransformer // use the previous block's color color = colorToApplyToNextBlock; colorToApplyToNextBlock = -1; - skyLight = skylightToApplyToNextBlock; - blockLight = blocklightToApplyToNextBlock; + + // use the skylight override if present + if (skylightToApplyToNextBlock != -1) + { + skyLight = skylightToApplyToNextBlock; + } + + if (blocklightToApplyToNextBlock != -1) + { + blockLight = blocklightToApplyToNextBlock; + } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java index c13c52c81..efc04641e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java @@ -193,6 +193,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu } this.fullDataSourceProvider.removeDataSourceUpdateListener(this); + this.genericRenderer.close(); } @@ -282,7 +283,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu @Override public void close() { - LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName()); + //LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName()); this.quadtree.close(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/DhApiRenderProxy.java b/core/src/main/java/com/seibel/distanthorizons/core/render/DhApiRenderProxy.java index a76c1e253..b630b6139 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/DhApiRenderProxy.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/DhApiRenderProxy.java @@ -77,6 +77,8 @@ public class DhApiRenderProxy implements IDhApiRenderProxy } } + // TODO clear tint handler too + return DhApiResult.createSuccess(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodQuadTree.java b/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodQuadTree.java index 297343424..44462b210 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodQuadTree.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodQuadTree.java @@ -1202,17 +1202,6 @@ public class LodQuadTree extends QuadTree implements IDebugRen { this.populateListWithEnabledRenderSections(this.debugNodeList); - //// can be uncommented for debugging/finding a specific position - //debugRenderer.makeParticle( - // new AbstractDebugWireframeRenderer.BoxParticle( - // new AbstractDebugWireframeRenderer.Box( - // DhSectionPos.encode((byte)7, 3,-1) - // , -64, 400, - // 0.1f, - // Color.YELLOW), - // 0.5, 0f - // )); - for (int i = 0; i < this.debugNodeList.size(); i++) { LodRenderSection renderSection = this.debugNodeList.get(i); @@ -1264,7 +1253,7 @@ public class LodQuadTree extends QuadTree implements IDebugRen @Override public void close() { - LOGGER.info("Shutting down LodQuadTree..."); + //LOGGER.info("Shutting down LodQuadTree..."); DEBUG_RENDERER.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus); Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this); @@ -1299,7 +1288,7 @@ public class LodQuadTree extends QuadTree implements IDebugRen }); - LOGGER.info("Finished shutting down LodQuadTree"); + //LOGGER.info("Finished shutting down LodQuadTree"); } //endregion base methods diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodRenderSection.java b/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodRenderSection.java index 1f870ebd4..fea3d51da 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodRenderSection.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodRenderSection.java @@ -36,6 +36,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer; +import com.seibel.distanthorizons.core.util.ExceptionUtil; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; @@ -62,7 +63,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable public final long pos; - private final IDhClientLevel level; + private final IDhClientLevel clientLevel; private final IClientLevelWrapper levelWrapper; @WillNotClose private final FullDataSourceProviderV2 fullDataSourceProvider; @@ -97,13 +98,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable */ private Runnable getAndBuildRenderDataRunnable = null; - /** - * Represents just uploading the {@link LodQuadBuilder} to the GPU.
- * Separate from {@link LodRenderSection#getAndBuildRenderDataFutureRef} because they run on - * different threads (buffer uploading is on the MC render thread) and need to be canceled separately. - */ - private final AtomicReference> bufferUploadFutureRef = new AtomicReference<>(null); - //=============// @@ -114,12 +108,12 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable public LodRenderSection( long pos, LodQuadTree quadTree, - IDhClientLevel level, FullDataSourceProviderV2 fullDataSourceProvider) + IDhClientLevel clientLevel, FullDataSourceProviderV2 fullDataSourceProvider) { this.pos = pos; this.quadTree = quadTree; - this.level = level; - this.levelWrapper = level.getClientLevelWrapper(); + this.clientLevel = clientLevel; + this.levelWrapper = clientLevel.getClientLevelWrapper(); this.fullDataSourceProvider = fullDataSourceProvider; DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus); @@ -161,6 +155,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable try { + // shouldn't happen since this method is synchronized, but just in case + // make sure we only ever start one upload task if (!this.getAndBuildRenderDataFutureRef.compareAndSet(null, future)) { CompletableFuture oldFuture = this.getAndBuildRenderDataFutureRef.get(); @@ -173,6 +169,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable { try { + // build LOD data on a DH thread LodQuadBuilder lodQuadBuilder = this.getAndBuildRenderData(); if (lodQuadBuilder == null) { @@ -180,7 +177,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable return; } - this.uploadToGpuAsync(lodQuadBuilder) + // uploading will primarily happen on the render thread + this.uploadToGpuAsync(future, lodQuadBuilder) .thenRun(() -> { // the future is passed in separately (IE not using the local var) to prevent any possible race condition null pointers @@ -190,7 +188,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable catch (Exception e) { LOGGER.error("Unexpected issue creating render data for pos: ["+DhSectionPos.toString(this.pos)+"], error: ["+e.getMessage()+"].", e); - future.complete(null); + future.completeExceptionally(e); } }; executor.execute(this.getAndBuildRenderDataRunnable); @@ -205,6 +203,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable return false; } } + + + //=======================// + // Get LOD ID data // + // and build render data // + //=======================// + //region + @Nullable private synchronized LodQuadBuilder getAndBuildRenderData() { @@ -218,7 +224,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled; - LodQuadBuilder lodQuadBuilder = new LodQuadBuilder(enableTransparency, this.level.getClientLevelWrapper()); + LodQuadBuilder lodQuadBuilder = new LodQuadBuilder(enableTransparency, this.clientLevel.getClientLevelWrapper()); // get the adjacent positions @@ -241,7 +247,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable // the render sources are only needed by this synchronous method, // then they can be closed - ColumnRenderBufferBuilder.makeLodRenderData(lodQuadBuilder, thisRenderSource, this.level, adjacentRenderSections, adjIsSameDetailLevel); + ColumnRenderBufferBuilder.makeLodRenderData(lodQuadBuilder, thisRenderSource, this.clientLevel, adjacentRenderSections, adjIsSameDetailLevel); return lodQuadBuilder; } catch (Exception e) @@ -291,53 +297,63 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; return detailLevel == DhSectionPos.getDetailLevel(this.pos); } - private synchronized CompletableFuture uploadToGpuAsync(LodQuadBuilder lodQuadBuilder) + + //endregion + + + private synchronized CompletableFuture uploadToGpuAsync( + CompletableFuture parentFuture, + LodQuadBuilder lodQuadBuilder) { - CompletableFuture oldFuture = this.bufferUploadFutureRef.getAndSet(null); - if (oldFuture != null) + CompletableFuture uploadFuture = LodBufferContainer.tryMakeAndUploadBuffersAsync(this.pos, this.clientLevel, lodQuadBuilder); + uploadFuture.whenComplete((bufferContainer, e) -> { - // canceling the previous future - // prevents the CPU from working on something that won't be used - oldFuture.cancel(true); - } - - CompletableFuture future = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, this.pos, lodQuadBuilder); - future.handle((lodBufferContainer, throwable) -> - { - if (!this.bufferUploadFutureRef.compareAndSet(future, null) - // if the old future is canceled then the future ref will be different and that's expected - && !future.isCancelled() - // if the old future is already done, then we don't care about the ref being swapped - && !future.isDone()) + try { - LOGGER.warn("Buffer upload future ref changed for pos: ["+DhSectionPos.toString(this.pos)+"]."); + // handle errors and early shutdown + if (e != null) + { + if (!ExceptionUtil.isShutdownException(e)) + { + LOGGER.error("Unexpected issue uploading buffers for pos: [" + DhSectionPos.toString(this.pos) + "], error: [" + e.getMessage() + "].", e); + } + + if (bufferContainer != null) + { + // shouldn't happen, but just in case + bufferContainer.close(); + } + return; + } + + // close the old container + LodBufferContainer oldContainer = this.renderBufferContainer; + this.renderBufferContainer = bufferContainer.buffersUploaded ? bufferContainer : null; + if (oldContainer != null) + { + oldContainer.close(); + } + + // upload complete + this.renderDataDirty = false; + + + if (parentFuture.isCancelled()) + { + // if the parent future was canceled that likely means + // this LodRenderSection was closed before this point, + // meaning this buffer will become homeless, + // so we need to clean it up here + bufferContainer.close(); + } } - - return null; - }); - - future.thenAccept((LodBufferContainer buffer) -> - { - // needed to clean up the old data - LodBufferContainer previousContainer = this.renderBufferContainer; - - // upload complete - this.renderBufferContainer = buffer.buffersUploaded ? buffer : null; - this.renderDataDirty = false; - - if (previousContainer != null) + catch (Exception finishEx) { - previousContainer.close(); + LOGGER.error("Unexpected buffer finish exception: ["+finishEx.getMessage()+"]", finishEx); } }); - - if (!this.bufferUploadFutureRef.compareAndSet(null, future)) - { - LodUtil.assertNotReach("Buffer upload future ref couldn't be set due to concurrency error, pos: ["+DhSectionPos.toString(this.pos)+"]."); - } - - return future; + return uploadFuture; } //endregion render data uploading @@ -391,8 +407,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable return; } - int levelMinY = this.level.getLevelWrapper().getMinHeight(); - int levelMaxY = this.level.getLevelWrapper().getMaxHeight(); + int levelMinY = this.clientLevel.getLevelWrapper().getMinHeight(); + int levelMaxY = this.clientLevel.getLevelWrapper().getMaxHeight(); // show the wireframe a bit lower than world max height, // since most worlds don't render all the way up to the max height @@ -429,13 +445,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable } - this.setRenderingEnabled(false); - if (this.renderBufferContainer != null) - { - this.renderBufferContainer.close(); - } - - // removes any in-progress futures since they aren't needed any more + // render loading is no longer needed CompletableFuture buildFuture = this.getAndBuildRenderDataFutureRef.get(); if (buildFuture != null) { @@ -451,12 +461,17 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable renderLoaderExecutor.remove(runnable); } } + + // cancel the future after removing the runnable + // to make sure the runnable is properly removed + buildFuture.cancel(true); } - CompletableFuture uploadFuture = this.bufferUploadFutureRef.get(); - if (uploadFuture != null) + + this.setRenderingEnabled(false); + if (this.renderBufferContainer != null) { - uploadFuture.cancel(true); + this.renderBufferContainer.close(); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/RenderParams.java b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderParams.java index a14c474b9..9d16062b9 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/RenderParams.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderParams.java @@ -172,45 +172,6 @@ public class RenderParams extends DhApiRenderParam } - //// potential fix for a segfault when - //// Sodium and DH are running together - //if (EPlatform.get() == EPlatform.MACOS - // && !initialLoadingComplete) - //{ - // // Once MC starts rendering, wait a few seconds so - // // MC/Sodium can finish their shader compiling before DH does its own. - // // This will allow DH to compile its own shaders after Sodium finishes - // // compiling its own. - // long nowMs = System.currentTimeMillis(); - // long firstAllowedRenderTimeMs = firstRenderTimeMs + TIME_FOR_MAC_TO_FINISH_COMPILING_IN_MS; - // if (nowMs < firstAllowedRenderTimeMs) - // { - // return "Waiting for initial MC compile..."; - // } - // - // - // // null shouldn't happen, but just in case - // PriorityTaskPicker.Executor renderLoadExecutor = ThreadPoolUtil.getRenderLoadingExecutor(); - // if (renderLoadExecutor == null) - // { - // return "Waiting for DH Threadpool..."; - // } - // - // // wait for DH to finish loading, by the time that's done - // // java should have finished all of DH's JIT compiling, - // // which will hopefully mean less concurrency and thus a lower - // // chance of breaking - // // (plus this gives Sodium/vanill a bit longer to finish their setup) - // int taskCount = renderLoadExecutor.getQueueSize(); - // if (taskCount > 0) - // { - // return "Waiting for DH JIT compiling..."; - // } - // - // initialLoadingComplete = true; - //} - - return null; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/RenderThreadTaskHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderThreadTaskHandler.java index 7d0bd380c..75c70a2eb 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/RenderThreadTaskHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderThreadTaskHandler.java @@ -10,6 +10,7 @@ import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import com.seibel.distanthorizons.coreapi.ModInfo; import org.jetbrains.annotations.Nullable; @@ -49,6 +50,14 @@ public class RenderThreadTaskHandler private long nanoSinceTasksRun = System.nanoTime(); + private final boolean running; + + private Thread renderThread; + /** + * the currently running {@link QueuedRunnable} + * will be null if nothing is running. + */ + private volatile @Nullable QueuedRunnable currentQueuedRunnable; @@ -57,7 +66,22 @@ public class RenderThreadTaskHandler //=============// //region - private RenderThreadTaskHandler() { TIMER.scheduleAtFixedRate(TimerUtil.createTimerTask(this::manualCleanupTick), MS_BETWEEN_CLEANUP_TICKS, MS_BETWEEN_CLEANUP_TICKS); } + private RenderThreadTaskHandler() + { + // we only want to run this when the client is available + IMinecraftSharedWrapper mcShared = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class); + if (!mcShared.isDedicatedServer()) + { + LOGGER.debug("Starting ["+RenderThreadTaskHandler.class.getSimpleName()+"]..."); + this.running = true; + TIMER.scheduleAtFixedRate(TimerUtil.createTimerTask(this::manualCleanupTick), MS_BETWEEN_CLEANUP_TICKS, MS_BETWEEN_CLEANUP_TICKS); + } + else + { + this.running = false; + LOGGER.debug("Skipping ["+RenderThreadTaskHandler.class.getSimpleName()+"] startup due to running on a dedicated server."); + } + } //endregion @@ -70,6 +94,13 @@ public class RenderThreadTaskHandler public void queueRunningOnRenderThread(String name, Runnable renderCall) { + // don't queuing tasks if they'll never be run + if (!this.running) + { + return; + } + + // don't get the stacktrace on release to reduce GC pressure StackTraceElement[] stackTrace = null; if (ModInfo.IS_DEV_BUILD) @@ -116,12 +147,21 @@ public class RenderThreadTaskHandler long loopStartTimeNano = System.nanoTime(); this.nanoSinceTasksRun = loopStartTimeNano; + + if (this.renderThread == null) + { + this.renderThread = Thread.currentThread(); + } + + QueuedRunnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll(); while(runnable != null) { long taskStartNano = System.nanoTime(); + this.currentQueuedRunnable = runnable; runnable.run(); + this.currentQueuedRunnable = null; // only try running for a limited amount of time to prevent lag spikes long taskNano = System.nanoTime() - taskStartNano; @@ -179,8 +219,19 @@ public class RenderThreadTaskHandler // this means we could have GL jobs building up. // Run the queued tasks on MC's executor (hopefully this should always run, // even if DH's render code isn't being hit). - IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - MC.executeOnRenderThread(() -> this.runRenderThreadTasks(500 * 1_000_000L)); + IMinecraftClientWrapper mcClient = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); + if (mcClient != null) + { + mcClient.executeOnRenderThread(() -> this.runRenderThreadTasks(500 * 1_000_000L)); + } + else + { + // shouldn't happen, but just in case + + // somehow the timer started when there wasn't a client wrapper + // this probably means the timer was started on a dedicated server + RATE_LIMITED_LOGGER.warn("["+RenderThreadTaskHandler.class.getSimpleName()+"] timer started when ["+IMinecraftClientWrapper.class.getSimpleName()+"] is null. This shouldn't happen but can likely be ignored."); + } } //endregion @@ -190,7 +241,7 @@ public class RenderThreadTaskHandler //===========// // debugging // //===========// - ///region + //region /** * if tasks are currently queued the debug @@ -246,7 +297,28 @@ public class RenderThreadTaskHandler }); } - ///endregion + + /** Returns true if the currently running thread is being run by this handler */ + public boolean isCurrentThread() + { + if (this.renderThread != null) + { + return Thread.currentThread() == this.renderThread; + } + + // shouldn't normally be needed, but can be used if this + // handler hasn't been run yet + return Thread.currentThread().getName().equals("Render thread"); + } + + /** + * Only recommended to be used by the task that's currently being run. + * Use {@link RenderThreadTaskHandler#isCurrentThread()} to check.
+ * Can be used to get stack traces for render thread tasks while they're being run. + */ + public @Nullable QueuedRunnable getCurrentlyRunningTask() { return this.currentQueuedRunnable; } + + //endregion @@ -255,7 +327,7 @@ public class RenderThreadTaskHandler //================// //region - private static class QueuedRunnable implements Runnable + public static class QueuedRunnable implements Runnable { /** used to easily track what's being done on the render thread */ public final String name; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java index 66b49f4fb..eca76f816 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java @@ -26,6 +26,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.render.DhApiRenderProxy; import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderParams; @@ -36,6 +37,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccess import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.*; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; +import java.awt.*; + /** * This is where all the magic happens.
* This is where LODs are draw to the world. @@ -288,6 +291,22 @@ public class LodRenderer } + if (Config.Client.Advanced.Debugging.PositionFinder.positionFinderEnable.get()) + { + // can be used to find specific positions when debugging + this.debugWireframeRenderer.renderBox(new AbstractDebugWireframeRenderer.Box( + DhSectionPos.encode( + Config.Client.Advanced.Debugging.PositionFinder.positionFinderDetailLevel.get().byteValue(), + Config.Client.Advanced.Debugging.PositionFinder.positionFinderXPos.get(), + Config.Client.Advanced.Debugging.PositionFinder.positionFinderZPos.get()), + Config.Client.Advanced.Debugging.PositionFinder.positionFinderMinBlockY.get(), + Config.Client.Advanced.Debugging.PositionFinder.positionFinderMaxBlockY.get(), + Config.Client.Advanced.Debugging.PositionFinder.positionFinderMarginPercent.get(), + Color.GREEN + )); + } + + //=============================// // Apply to the MC Framebuffer // diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/ExceptionUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/ExceptionUtil.java index 916961af6..a6f8f4b08 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/ExceptionUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/ExceptionUtil.java @@ -25,6 +25,7 @@ public class ExceptionUtil return throwable instanceof InterruptedException || throwable instanceof UncheckedInterruptedException || throwable instanceof RejectedExecutionException + || throwable instanceof CancellationException || throwable instanceof ClosedByInterruptException; } @@ -37,8 +38,8 @@ public class ExceptionUtil unwrapped instanceof CancellationException; } public static Throwable ensureUnwrap(Throwable t) - { - return t instanceof CompletionException ? ensureUnwrap(t.getCause()) : t; - } + { return t instanceof CompletionException ? ensureUnwrap(t.getCause()) : t; } + + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java index 3bd332676..baac95b93 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java @@ -451,6 +451,21 @@ public class RenderDataPointReducingList extends AbstractPhantomArrayList this.setBigger(smaller, bigger); } + if (writeIndex == 0) + { + // if every data point in the list is NULL (0) the write index will be 0, + // and in order to prevent accessing index -1 below, + // setting the write index to 1 is needed. + + // This shouldn't happen normally, however if the lod data is slightly malformed + // (which is specifically the case for the commonly shared wyncraft LODs) + // this check is needed. + // It would probably be best to fix the 6 or so NULL datapoints that are next + // to each other in the full data source, but for now this fix works. + + writeIndex = 1; + } + this.smallest = this.sortingArray.getShort(0); this.biggest = this.sortingArray.getShort(writeIndex - 1); this.setSmaller(this.getSmallest(), NULL); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/pooling/PhantomArrayListPool.java b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/pooling/PhantomArrayListPool.java index f266006ca..6e15121ac 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/pooling/PhantomArrayListPool.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/pooling/PhantomArrayListPool.java @@ -331,7 +331,7 @@ public class PhantomArrayListPool if (pool.logGarbageCollectedStacks && checkout.allocationStackTrace != null) // stack trace shouldn't be null, but just in case { - putAndIncrementTrackingString(checkout.allocationStackTrace, allocationStackTraceCountPairList); + PhantomLoggingHelper.putAndIncrementTrackingString(checkout.allocationStackTrace, allocationStackTraceCountPairList); } } else @@ -363,18 +363,7 @@ public class PhantomArrayListPool // log stack traces if present if (pool.logGarbageCollectedStacks) { - // high numbers first - allocationStackTraceCountPairList.sort((a, b) -> Integer.compare(b.second.get(), a.second.get())); - - StringBuilder stringBuilder = new StringBuilder(); - for (int j = 0; j < allocationStackTraceCountPairList.size(); j++) - { - int count = allocationStackTraceCountPairList.get(j).second.get(); - String stack = allocationStackTraceCountPairList.get(j).first; - - stringBuilder.append(count).append(". ").append(stack).append("\n"); - } - LOGGER.warn("Stacks: ["+ allocationStackTraceCountPairList.size()+"]\n" + stringBuilder.toString()); + PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, allocationStackTraceCountPairList); } } } @@ -389,36 +378,6 @@ public class PhantomArrayListPool } } } - /** - * This was separated out so it could be used for other string pair lists. - * James originally had an idea to add a shorter static string - * ID to each allocated {@link PhantomArrayListCheckout} as a simpler version of the stack trace, - * however it became a bit more difficult and messy than he wanted to deal with, so for now we just - * have the stack trace. - */ - private static void putAndIncrementTrackingString( - String key, - ArrayList> allocationStackTraceCountPairList) - { - // sequential search, for the number of elements we're dealing with (less than 20) - // this should be sufficiently fast - boolean pairFound = false; - for (int i = 0; i < allocationStackTraceCountPairList.size(); i++) - { - Pair possiblePair = allocationStackTraceCountPairList.get(i); - if (possiblePair.first.equals(key)) - { - possiblePair.second.getAndIncrement(); - pairFound = true; - break; - } - } - - if (!pairFound) - { - allocationStackTraceCountPairList.add(new Pair<>(key, new AtomicInteger(1))); - } - } ///endregion diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/pooling/PhantomLoggingHelper.java b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/pooling/PhantomLoggingHelper.java new file mode 100644 index 000000000..4adeab225 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/pooling/PhantomLoggingHelper.java @@ -0,0 +1,232 @@ +package com.seibel.distanthorizons.core.util.objects.pooling; + +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.logging.f3.F3Screen; +import com.seibel.distanthorizons.core.util.ThreadUtil; +import com.seibel.distanthorizons.core.util.objects.Pair; + +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicInteger; + +public class PhantomLoggingHelper +{ + /** + * This was separated out so it could be used for other string pair lists. + * James originally had an idea to add a shorter static string + * ID to each allocated {@link PhantomArrayListCheckout} as a simpler version of the stack trace, + * however it became a bit more difficult and messy than he wanted to deal with, so for now we just + * have the stack trace. + */ + public static void putAndIncrementTrackingString( + String key, + ArrayList> allocationStackTraceCountPairList) + { + // sequential search, for the number of elements we're dealing with (less than 20) + // this should be sufficiently fast + boolean pairFound = false; + for (int i = 0; i < allocationStackTraceCountPairList.size(); i++) + { + Pair possiblePair = allocationStackTraceCountPairList.get(i); + if (possiblePair.first.equals(key)) + { + possiblePair.second.getAndIncrement(); + pairFound = true; + break; + } + } + + if (!pairFound) + { + allocationStackTraceCountPairList.add(new Pair<>(key, new AtomicInteger(1))); + } + } + + public static void LogAllocationStackTracePairCounts(DhLogger logger, ArrayList> allocationStackTraceCountPairList) + { + // high numbers first + allocationStackTraceCountPairList.sort((a, b) -> Integer.compare(b.second.get(), a.second.get())); + + StringBuilder stringBuilder = new StringBuilder(); + for (int j = 0; j < allocationStackTraceCountPairList.size(); j++) + { + int count = allocationStackTraceCountPairList.get(j).second.get(); + String stack = allocationStackTraceCountPairList.get(j).first; + + stringBuilder.append(count).append(". ").append(stack).append("\n"); + } + logger.warn("Stacks: ["+ allocationStackTraceCountPairList.size()+"]\n" + stringBuilder.toString()); + } + + + + //================// + // helper classes // + //================// + //region + + /** + * Can be quickly added to a {@link AutoCloseable} implementing + * class to confirm it's being properly closed. + */ + public static class BasicPhantomReference implements AutoCloseable + { + private static final DhLogger LOGGER = new DhLoggerBuilder().build(); + + /** if enabled the number of GC'ed buffers will be logged */ + private static final boolean LOG_PHANTOM_RECOVERY = true; + /** + * If enabled the GC'ed buffers allocation/upload stacks will be logged. + * Note: due to how the buffers are often run on the render thread, + * these stacks will likely only be of limited use. + * For more robust debugging it would likely be best to somehow track + * the stacks of where these calls are happening before they're queued + * for the render thread. + */ + private static final boolean LOG_PHANTOM_ALLOCATION_STACKS = true; + + private static final int PHANTOM_REF_CHECK_TIME_IN_MS = 5 * 1000; + private static final ReferenceQueue PHANTOM_REFERENCE_QUEUE = new ReferenceQueue<>(); + private static final ConcurrentHashMap, Class> PHANTOM_TO_PARENT_CLASS = new ConcurrentHashMap<>(); + + private static final ThreadPoolExecutor CLEANUP_THREAD = ThreadUtil.makeSingleDaemonThreadPool("BasicPhantom Cleanup"); + + + private final Class parentClass; + private final PhantomReference phantomReference; + + + + //==============// + // constructors // + //==============// + //region + + static { CLEANUP_THREAD.execute(() -> runPhantomReferenceCleanupLoop()); } + + public BasicPhantomReference(Class parentClass) + { + this.parentClass = parentClass; + this.phantomReference = new PhantomReference<>(this, PHANTOM_REFERENCE_QUEUE); + PHANTOM_TO_PARENT_CLASS.put(this.phantomReference, this.parentClass); + } + + //endregion + + + + //================// + // base overrides // + //================// + //region + + @Override + public void close() + { + this.phantomReference.clear(); + PHANTOM_TO_PARENT_CLASS.remove(this.phantomReference); + } + + //endregion + + + + //================// + // static cleanup // + //================// + //region + + private static void runPhantomReferenceCleanupLoop() + { + // these arrays are stored here so they don't have to be re-allocated each loop + ArrayList> allocationStackTraceCountPairList = new ArrayList<>(); + ArrayList> parentClassNameCountPairList = new ArrayList<>(); + + while (true) + { + allocationStackTraceCountPairList.clear(); + parentClassNameCountPairList.clear(); + + try + { + try + { + Thread.sleep(PHANTOM_REF_CHECK_TIME_IN_MS); + } + catch (InterruptedException ignore) { } + + int collectedCount = 0; + + Reference phantomRef = PHANTOM_REFERENCE_QUEUE.poll(); + while (phantomRef != null) + { + // destroy the buffer if it hasn't been cleared yet + Class parentClass = PHANTOM_TO_PARENT_CLASS.remove((PhantomReference)phantomRef); // cast to make IntelliJ happy + { + String parentClassName = "NULL"; + if (parentClass != null) + { + parentClassName = parentClass.getSimpleName(); + } + + PhantomLoggingHelper.putAndIncrementTrackingString(parentClassName, parentClassNameCountPairList); + //LOGGER.info("Phantom collected for class: [" + parentClassName + "]"); + } + + + //if (LOG_PHANTOM_ALLOCATION_STACKS) // stack trace shouldn't be null, but just in case + //{ + // String stack = BUFFER_ID_TO_ALLOCATION_STRING.get(idRef); + // if (stack != null) + // { + // PhantomLoggingHelper.putAndIncrementTrackingString(stack, allocationStackTraceCountPairList); + // } + //} + + + collectedCount++; + phantomRef = PHANTOM_REFERENCE_QUEUE.poll(); + } + + + + if (LOG_PHANTOM_RECOVERY) + { + // we only want to log when something has been returned + if (collectedCount != 0) + { + LOGGER.warn("Phantoms collected: ["+ F3Screen.NUMBER_FORMAT.format(collectedCount)+"]."); + + PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, parentClassNameCountPairList); + + //// log stack traces if present + //if (LOG_PHANTOM_ALLOCATION_STACKS) + //{ + // PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, allocationStackTraceCountPairList); + //} + } + } + + } + catch (Exception e) + { + LOGGER.error("Unexpected error in buffer cleanup thread: [" + e.getMessage() + "].", e); + } + } + } + + //endregion + + + } + + //endregion + + + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/RateLimitedThreadPoolExecutor.java b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/RateLimitedThreadPoolExecutor.java index ad142d2e9..aa8203c3d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/RateLimitedThreadPoolExecutor.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/RateLimitedThreadPoolExecutor.java @@ -73,10 +73,17 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor { super.afterExecute(runnable, throwable); + double ratio = this.runTimeRatioConfig.get(); + if (ratio >= 1.0) + { + // Avoid sleeping for 0 time + return; + } + try { long runTime = System.nanoTime() - this.runStartTime.get(); - Thread.sleep(TimeUnit.NANOSECONDS.toMillis((long) (runTime / this.runTimeRatioConfig.get() - runTime))); + Thread.sleep(TimeUnit.NANOSECONDS.toMillis((long) (runTime / ratio - runTime))); } catch (InterruptedException ignore) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/render/renderPass/IDhGenericRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/render/renderPass/IDhGenericRenderer.java index 56f6cc6e5..c0853415b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/render/renderPass/IDhGenericRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/render/renderPass/IDhGenericRenderer.java @@ -23,10 +23,14 @@ import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegist import com.seibel.distanthorizons.core.render.RenderParams; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; -public interface IDhGenericRenderer extends IDhApiCustomRenderRegister +public interface IDhGenericRenderer extends IDhApiCustomRenderRegister, AutoCloseable { void render(RenderParams renderEventParam, IProfilerWrapper profiler, boolean renderingWithSsao); String getVboRenderDebugMenuString(); + @Override void close(); // override to remove "throws exception" + + + } 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 8dbce10fd..4887d3807 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -188,11 +188,11 @@ "If true non terrain objects will be rendered in DH's terrain. \nThis includes beacon beams and clouds.", "distanthorizons.config.client.advanced.graphics.genericRendering.enableBeaconRendering": "Enable Beacon Rendering", - "distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight": + "distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight": "Beacon render height", "distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight.@tooltip": "Sets the maximum height at which beacons will render. \nThis will only affect new beacons coming into LOD render distance. \nBeacons currently visible in LOD chunks will not be affected.", - "distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons": + "distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons": "Expand Distant Beacons", "distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons.@tooltip": "If true LOD beacon beams will be rendered wider at extreme distances, \nmaking them easier to see. \nIf false all LOD beacon beams will only ever be 1 block wide.", @@ -533,6 +533,26 @@ + "distanthorizons.config.client.advanced.debugging.positionFinderDebugging": + "Position Finder", + + "distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderEnable": + "Enable Position Finder", + "distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderDetailLevel": + "Absolute Detail Level", + "distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderXPos": + "Pos X", + "distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderZPos": + "Pos Z", + "distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderMinBlockY": + "Min Block Y", + "distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderMaxBlockY": + "Max Block Y", + "distanthorizons.config.client.advanced.debugging.positionFinderDebugging.positionFinderMarginPercent": + "Margin %", + + + "distanthorizons.config.client.advanced.debugging.f3Screen": "F3 Screen", diff --git a/core/src/test/java/tests/PooledDataSourceCheckoutTest.java b/core/src/test/java/tests/PooledDataSourceCheckoutTest.java index 7a0eae61f..7cbacf3b0 100644 --- a/core/src/test/java/tests/PooledDataSourceCheckoutTest.java +++ b/core/src/test/java/tests/PooledDataSourceCheckoutTest.java @@ -39,7 +39,13 @@ import org.junit.Test; public class PooledDataSourceCheckoutTest { - @Test + /** + * commented out for now since it has + * a chance of breaking if any other tests + * using the same pools run at the same time + * or before this one. + */ + //@Test public void TestCheckouts() { // something like this should probably be called before starting the test to ensure