Compare commits

...

22 Commits

Author SHA1 Message Date
James Seibel 269f2c30fd Initial changes for Vulkan 2026-05-11 21:56:53 -05:00
James Seibel b674f49600 up version number 3.0.3 -> 3.0.4 2026-05-04 07:41:32 -05:00
James Seibel b592012ba8 remove dev from version number 2026-05-03 18:20:22 -05:00
James Seibel 5d1e8a44fd up api version 6.1.0 -> 6.1.1 2026-05-03 18:20:12 -05:00
James Seibel 40b27335ea Add stack getting for render tasks 2026-05-03 16:45:23 -05:00
James Seibel d0b07a5d2f remove accidental debug code 2026-05-03 16:40:35 -05:00
James Seibel cb0fee9780 fix generic renderer buffer leak on level close 2026-05-03 16:36:32 -05:00
James Seibel 895e9276cd Fix GL buffer GC in RenderContainer canceling 2026-05-03 15:46:01 -05:00
James Seibel 9ee0af8b01 Add BasicPhantomReference for debugging 2026-05-03 15:45:52 -05:00
James Seibel 69941fb7f8 DhApiBlockColorOverrideEvent use default alpha 2026-05-02 21:15:48 -05:00
James Seibel 36862a968f fix rare skylight application bug 2026-05-02 21:14:54 -05:00
James Seibel 27204336b2 cleanup lod buffer container closing 2026-05-02 21:14:14 -05:00
James Seibel 4846cf5019 comment out unnecessary shutdown logging 2026-05-02 21:13:07 -05:00
James Seibel f7f3c1146f separate shared phantom logging logic 2026-05-02 21:12:26 -05:00
James Seibel aaa5e958f0 Fix LOD shading applying incorrectly with Iris 2026-05-02 15:14:25 -05:00
James Seibel 726da953bd Merge branch 'distant-horizons-core-optimizations' 2026-05-02 11:35:26 -05:00
James Seibel c4f4935fdd Remove unused mac render code 2026-05-02 10:36:44 -05:00
James Seibel 3ef8bd7e20 Add position finder debug config 2026-04-29 07:35:16 -05:00
James Seibel ec72762067 use camera pos for detail calculations 2026-04-28 07:09:22 -05:00
James Seibel 4d0ed2a6dc fix null pointer on dedicated server shutdown 2026-04-27 07:48:06 -05:00
James Seibel 7b252b173b Fix wyncraft getting stuck at low LOD quality 2026-04-27 07:27:03 -05:00
James Seibel 7b0c66e3ae up version number 3.0.2 -> 3.0.3 2026-04-24 06:51:39 -05:00
27 changed files with 803 additions and 458 deletions
@@ -111,7 +111,7 @@ public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent<DhApiB
public int getRed() { return ColorUtil.getRed(this.colorAsInt); } public int getRed() { return ColorUtil.getRed(this.colorAsInt); }
public int getGreen() { return ColorUtil.getGreen(this.colorAsInt); } public int getGreen() { return ColorUtil.getGreen(this.colorAsInt); }
public int getBlue() { return ColorUtil.getBlue(this.colorAsInt); } public int getBlue() { return ColorUtil.getBlue(this.colorAsInt); }
public void setColor(int red, int green, int blue) throws IllegalArgumentException { setColor(255, red, green, blue); } public void setColor(int red, int green, int blue) throws IllegalArgumentException { this.setColor(this.getAlpha(), red, green, blue); }
/** /**
* Note: when if you set a partially transparent alpha channel the underlying {@link IDhApiBlockStateWrapper#getOpacity()} * Note: when if you set a partially transparent alpha channel the underlying {@link IDhApiBlockStateWrapper#getOpacity()}
* method should also return a non-opaque value. * method should also return a non-opaque value.
@@ -35,7 +35,7 @@ import java.util.Map;
*/ */
public class DependencyInjector<BindableType extends IBindable> implements IDependencyInjector<BindableType> // Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it public class DependencyInjector<BindableType extends IBindable> implements IDependencyInjector<BindableType> // Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it
{ {
protected final Map<Class<? extends BindableType>, ArrayList<BindableType>> dependencies = new HashMap<>(); protected final HashMap<Class<? extends BindableType>, ArrayList<BindableType>> dependencies = new HashMap<>();
/** Internal class reference to BindableType since we can't get it any other way. */ /** Internal class reference to BindableType since we can't get it any other way. */
protected final Class<? extends BindableType> bindableInterface; protected final Class<? extends BindableType> bindableInterface;
@@ -43,7 +43,7 @@ public final class ModInfo
public static final String NAME = "DistantHorizons"; public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */ /** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons"; public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "3.0.2-b"; public static final String VERSION = "3.0.4-b-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */ /** Returns true if the current build is an unstable developer build, false otherwise. */
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev"); 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 */ /** This version should be updated whenever new methods are added to the DH API */
public static final int API_MINOR_VERSION = 1; public static final int API_MINOR_VERSION = 1;
/** This version should be updated whenever non-breaking fixes are added to the DH API */ /** 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. */ /** If the config file has an older version it'll be re-created from scratch. */
public static final int CONFIG_FILE_VERSION = 4; public static final int CONFIG_FILE_VERSION = 4;
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*; import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState; import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure; import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -40,6 +41,7 @@ import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
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.IDhMetaRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhVanillaFadeRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhVanillaFadeRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTestTriangleRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTestTriangleRenderer;
@@ -63,6 +65,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import java.awt.*;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
@@ -147,6 +150,15 @@ public class ClientApi
public static long firstRenderTimeMs = 0; 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;
//==============// //==============//
@@ -160,7 +172,7 @@ public class ClientApi
//==============// //==============//
// world events // // world events //
//==============// //==============//
//region //region world events
/** /**
* May be fired slightly before or after the associated * May be fired slightly before or after the associated
@@ -245,7 +257,7 @@ public class ClientApi
//==============// //==============//
// level events // // level events //
//==============// //==============//
//region //region level events
public void clientLevelUnloadEvent(IClientLevelWrapper level) public void clientLevelUnloadEvent(IClientLevelWrapper level)
{ {
@@ -364,7 +376,7 @@ public class ClientApi
//============// //============//
// networking // // networking //
//============// //============//
//region //region networking
/** /**
* Forwards a decoded message into the registered handlers. * Forwards a decoded message into the registered handlers.
@@ -404,7 +416,7 @@ public class ClientApi
//===============// //===============//
// LOD rendering // // LOD rendering //
//===============// //===============//
//region //region lod rendering
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */ /** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
public void renderLods() { this.renderLodLayer(false); } public void renderLods() { this.renderLodLayer(false); }
@@ -428,12 +440,16 @@ public class ClientApi
//===========// //===========//
//region //region
// only run these tasks once per frame
if (!renderingDeferredLayer)
{
//DhApiTerrainDataRepo.asyncDebugMethod( //DhApiTerrainDataRepo.asyncDebugMethod(
// RENDER_STATE.clientLevelWrapper, // RENDER_STATE.clientLevelWrapper,
// MC_CLIENT.getPlayerBlockPos().getX(), // MC_CLIENT.getPlayerBlockPos().getX(),
// MC_CLIENT.getPlayerBlockPos().getY(), // MC_CLIENT.getPlayerBlockPos().getY(),
// MC_CLIENT.getPlayerBlockPos().getZ() // MC_CLIENT.getPlayerBlockPos().getZ()
//); //);
}
//endregion //endregion
@@ -499,6 +515,27 @@ public class ClientApi
} }
//endregion //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
} }
} }
@@ -667,7 +704,7 @@ public class ClientApi
//================// //================//
// fade rendering // // fade rendering //
//================// //================//
//region //region fade rendering
/** /**
* The first fade pass. * The first fade pass.
@@ -737,7 +774,7 @@ public class ClientApi
//==========// //==========//
// keyboard // // keyboard //
//==========// //==========//
//region //region keyboard
/** Trigger once on key press, with CLIENT PLAYER. */ /** Trigger once on key press, with CLIENT PLAYER. */
public void keyPressedEvent(int glfwKey) public void keyPressedEvent(int glfwKey)
@@ -773,7 +810,7 @@ public class ClientApi
//======// //======//
// chat // // chat //
//======// //======//
//region //region chat
private void sendQueuedChatMessages() private void sendQueuedChatMessages()
{ {
@@ -964,6 +964,7 @@ public class Config
public static ConfigCategory debugWireframe = new ConfigCategory.Builder().set(DebugWireframe.class).build(); 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 openGl = new ConfigCategory.Builder().set(OpenGl.class).build();
public static ConfigCategory columnBuilderDebugging = new ConfigCategory.Builder().set(ColumnBuilderDebugging.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 f3Screen = new ConfigCategory.Builder().set(F3Screen.class).build();
public static ConfigCategory exampleConfigScreen = new ConfigCategory.Builder().set(ExampleConfigScreen.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<Boolean> positionFinderEnable = new ConfigEntry.Builder<Boolean>()
.set(false)
.build();
public static ConfigEntry<Integer> positionFinderDetailLevel = new ConfigEntry.Builder<Integer>()
.set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
.build();
public static ConfigEntry<Integer> positionFinderXPos = new ConfigEntry.Builder<Integer>()
.set(0)
.build();
public static ConfigEntry<Integer> positionFinderZPos = new ConfigEntry.Builder<Integer>()
.set(0)
.build();
public static ConfigEntry<Integer> positionFinderMinBlockY = new ConfigEntry.Builder<Integer>()
.set(-64)
.build();
public static ConfigEntry<Integer> positionFinderMaxBlockY = new ConfigEntry.Builder<Integer>()
.set(125)
.build();
public static ConfigEntry<Float> positionFinderMarginPercent = new ConfigEntry.Builder<Float>()
.set(0.0f)
.build();
}
public static class F3Screen public static class F3Screen
{ {
public static ConfigUIComment f3ScreenHeader = new ConfigUIComment.Builder().setParentConfigClass(F3Screen.class).build(); public static ConfigUIComment f3ScreenHeader = new ConfigUIComment.Builder().setParentConfigClass(F3Screen.class).build();
@@ -59,9 +59,19 @@ public final class BufferQuad
public boolean hasError = false; 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, short x, short y, short z, short widthEastWest, short widthNorthSouthOrHeight,
int color, byte irisBlockMaterialId, byte skylight, byte blockLight, int color, byte irisBlockMaterialId, byte skylight, byte blockLight,
EDhDirection direction) EDhDirection direction)
@@ -85,64 +95,46 @@ public final class BufferQuad
this.skyLight = skylight; this.skyLight = skylight;
this.blockLight = blockLight; this.blockLight = blockLight;
this.direction = direction; this.direction = direction;
this.sortKeyEastWest = computeSortKey(direction, true);
this.sortKeyNorthSouth = computeSortKey(direction, false);
} }
private long computeSortKey(EDhDirection dir, boolean eastWest)
/** a rough but fast calculation */
double calculateDistance(double relativeX, double relativeY, double relativeZ)
{ {
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) public int compare(BufferQuad quad, BufferMergeDirectionEnum compareDirection)
{ {
if (this.direction != quad.direction) if (this.direction != quad.direction)
throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + this.direction); throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + this.direction);
if (compareDirection == BufferMergeDirectionEnum.EastWest) return compareDirection == BufferMergeDirectionEnum.EastWest
{ ? Long.compare(this.sortKeyEastWest, quad.sortKeyEastWest)
switch (this.direction.axis) : Long.compare(this.sortKeyNorthSouth, quad.sortKeyNorthSouth);
{
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. <br>
* 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. <br>
* 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);
} }
@@ -154,11 +146,15 @@ public final class BufferQuad
public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection) public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection)
{ {
if (quad.hasError || this.hasError) if (quad.hasError || this.hasError)
{
return false; return false;
}
// only merge quads that are in the same direction // only merge quads that are in the same direction
if (this.direction != quad.direction) if (this.direction != quad.direction)
{
return false; return false;
}
// make sure these quads share the same perpendicular axis // make sure these quads share the same perpendicular axis
if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y) if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y)
@@ -175,7 +171,6 @@ public final class BufferQuad
short otherParallelCompareStartPos; short otherParallelCompareStartPos;
switch (this.direction.axis) switch (this.direction.axis)
{ {
default: // shouldn't normally happen, just here to make the compiler happy
case X: case X:
if (mergeDirection == BufferMergeDirectionEnum.EastWest) if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{ {
@@ -232,6 +227,9 @@ public final class BufferQuad
otherParallelCompareStartPos = quad.z; otherParallelCompareStartPos = quad.z;
} }
break; 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 // get the width of this quad in the relevant axis
@@ -333,4 +331,6 @@ public final class BufferQuad
return true; return true;
} }
} }
@@ -341,13 +341,24 @@ public class ColumnBox
// Apply light to the range [adjMinY, adjMaxY) // 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 // Fill overhang area [adjMaxY, adjAboveMinY) with adjSkyLight
short adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint); short adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint);
if (adjMaxY < adjAboveMinY) 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, * Apply the new light value over the given y range,
* splitting segments as needed * splitting segments as needed
* and putting the new segments into "newSegments"
* <p> * <p>
* source: claude.ai * source: claude.ai
*/ */
private static void applyLightToRange( private static void applyLightToRangeAndPopulateNewSgements(
LongArrayList segments, LongArrayList newSegments, LongArrayList segments, LongArrayList newSegments,
short rangeStart, short rangeEnd, short rangeStart, short rangeEnd,
byte newLight) byte newLight)
@@ -419,9 +431,6 @@ public class ColumnBox
newSegments.add(YSegmentUtil.encode(rangeEnd, endY, skyLight)); newSegments.add(YSegmentUtil.encode(rangeEnd, endY, skyLight));
} }
} }
segments.clear();
segments.addAll(newSegments);
} }
private static void tryAddVerticalFaceWithSkyLightToBuilder( private static void tryAddVerticalFaceWithSkyLightToBuilder(
@@ -55,26 +55,6 @@ public class ColumnRenderBufferBuilder
// vbo building // // vbo building //
//==============// //==============//
/** @link adjData should be null for adjacent sections that cross detail level boundaries */
public static CompletableFuture<LodBufferContainer> 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<LodBufferContainer> 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( public static void makeLodRenderData(
LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel, LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel,
ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel) ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel)
@@ -20,13 +20,13 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; 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.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.util.ExceptionUtil; 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.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition; import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper;
@@ -37,7 +37,6 @@ import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* Java representation of one or more OpenGL buffers for rendering. * 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(); public ILodContainerUniformBufferWrapper uniformContainer = WRAPPER_FACTORY.createLodContainerUniformWrapper();
private final AtomicReference<CompletableFuture<LodBufferContainer>> uploadFutureRef = new AtomicReference<>(null);
//==============// //==============//
@@ -72,7 +69,7 @@ public class LodBufferContainer implements AutoCloseable
//==============// //==============//
//region //region
public LodBufferContainer(long pos, DhBlockPos minCornerBlockPos) private LodBufferContainer(long pos, DhBlockPos minCornerBlockPos)
{ {
this.pos = pos; this.pos = pos;
this.minCornerBlockPos = minCornerBlockPos; this.minCornerBlockPos = minCornerBlockPos;
@@ -92,41 +89,12 @@ public class LodBufferContainer implements AutoCloseable
//region //region
/** Should be run on a DH thread. */ /** Should be run on a DH thread. */
public synchronized CompletableFuture<LodBufferContainer> tryMakeAndUploadBuffersAsync(LodQuadBuilder builder) public static CompletableFuture<LodBufferContainer> tryMakeAndUploadBuffersAsync(
long pos, IDhClientLevel clientLevel,
LodQuadBuilder builder)
{ {
//================//
// handle futures //
//================//
//region
// separate variable to prevent race condition when checking null
CompletableFuture<LodBufferContainer> oldFuture = this.uploadFutureRef.get();
if (oldFuture != null)
{
// upload already in process
return oldFuture;
}
// new upload needed // new upload needed
CompletableFuture<LodBufferContainer> future = new CompletableFuture<>(); CompletableFuture<LodBufferContainer> 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 //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<ByteBuffer> opaqueBuffers = builder.makeOpaqueVertexBuffers(); ArrayList<ByteBuffer> opaqueBuffers = builder.makeOpaqueVertexBuffers();
ArrayList<ByteBuffer> transparentBuffers = builder.makeTransparentVertexBuffers(); ArrayList<ByteBuffer> transparentBuffers = builder.makeTransparentVertexBuffers();
this.vboOpaqueWrappers = resizeWrapperArray(this.vboOpaqueWrappers, opaqueBuffers.size()); // update arrays to contain buffers
this.vboTransparentWrappers = resizeWrapperArray(this.vboTransparentWrappers, transparentBuffers.size()); 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 // all other OS's can share a single IBO for quicker loading times
boolean useSingleIbo = RENDER_DEF.useSingleIbo(); boolean useSingleIbo = RENDER_DEF.useSingleIbo();
@Nullable ArrayList<ByteBuffer> opaqueIndexBuffers = useSingleIbo ? null : this.createIndexBuffers(opaqueBuffers); @Nullable ArrayList<ByteBuffer> opaqueIndexBuffers = useSingleIbo ? null : bufferContainer.createIndexBuffers(opaqueBuffers);
@Nullable ArrayList<ByteBuffer> transparentIndexBuffers = useSingleIbo ? null : this.createIndexBuffers(transparentBuffers); @Nullable ArrayList<ByteBuffer> transparentIndexBuffers = useSingleIbo ? null : bufferContainer.createIndexBuffers(transparentBuffers);
//endregion //endregion
//================//
// upload buffers //
//================//
//region
try
{
//=============// //=============//
// create VBOs // // create VBOs //
//=============// //=============//
//region
CompletableFuture<Void> createOpaqueFuture = createBufferWrappersAsync(future, this.vboOpaqueWrappers, opaqueBuffers); CompletableFuture<Void> createFuture = new CompletableFuture<Void>();
CompletableFuture<Void> createTransparentFuture = createBufferWrappersAsync(future, this.vboTransparentWrappers, transparentBuffers); RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Setup", () ->
CompletableFuture<Void> createFuture = CompletableFuture.allOf(createOpaqueFuture, createTransparentFuture);
createFuture.exceptionally((Throwable e) ->
{ {
// create VBOs failed // try
if (!ExceptionUtil.isShutdownException(e))
{ {
LOGGER.error("Unexpected issue creating buffer [" + this.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e); // skip this event if requested
if (Thread.interrupted()
|| future.isCancelled())
{
throw new InterruptedException();
} }
future.completeExceptionally(e);
return null;
});
createFuture.thenRun(() ->
{
//=============//
// upload VBOs //
//=============//
CompletableFuture<Void> opaqueFuture = uploadBuffersAsync(future, this.vboOpaqueWrappers, opaqueBuffers, opaqueIndexBuffers); createBufferWrappers(bufferContainer.vboOpaqueWrappers, opaqueBuffers);
CompletableFuture<Void> transparentFuture = uploadBuffersAsync(future, this.vboTransparentWrappers, transparentBuffers, transparentIndexBuffers); createBufferWrappers(bufferContainer.vboTransparentWrappers, transparentBuffers);
CompletableFuture<Void> uploadFuture = CompletableFuture.allOf(opaqueFuture, transparentFuture); createFuture.complete(null);
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);
});
});
} }
catch (Exception e) catch (Exception e)
{ {
if (!ExceptionUtil.isShutdownException(e)) if (!ExceptionUtil.isShutdownException(e))
{ {
LOGGER.error("Unexpected issue prepping buffer uploading [" + 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); future.completeExceptionally(e);
return null;
});
createFuture.thenRun(() ->
{
CompletableFuture<Void> opaqueFuture = uploadBuffersAsync(future, bufferContainer.vboOpaqueWrappers, opaqueBuffers, opaqueIndexBuffers);
CompletableFuture<Void> transparentFuture = uploadBuffersAsync(future, bufferContainer.vboTransparentWrappers, transparentBuffers, transparentIndexBuffers);
CompletableFuture<Void> 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;
});
uploadFuture.thenRun(() ->
{
// upload success //
bufferContainer.buffersUploaded = true;
future.complete(bufferContainer);
});
});
//================// //endregion
// buffer cleanup //
//================//
//====================//
// CPU Buffer cleanup //
//====================//
//region
future.whenComplete((LodBufferContainer lodBufferContainer, Throwable throwable) -> future.whenComplete((LodBufferContainer lodBufferContainer, Throwable throwable) ->
{ {
@@ -290,11 +286,8 @@ public class LodBufferContainer implements AutoCloseable
return newVbos; return newVbos;
} }
private static CompletableFuture<Void> createBufferWrappersAsync( private static void createBufferWrappers(IVertexBufferWrapper[] vboWrappers, ArrayList<ByteBuffer> vertexBuffers)
CompletableFuture<LodBufferContainer> parentFuture,
IVertexBufferWrapper[] vboWrappers, ArrayList<ByteBuffer> vertexBuffers)
{ {
ArrayList<CompletableFuture<Void>> createVboFutureList = new ArrayList<>();
for (int i = 0; i < vertexBuffers.size(); i++) for (int i = 0; i < vertexBuffers.size(); i++)
{ {
if (i >= vboWrappers.length) if (i >= vboWrappers.length)
@@ -304,45 +297,9 @@ public class LodBufferContainer implements AutoCloseable
if (vboWrappers[i] == null) if (vboWrappers[i] == null)
{ {
final int finalVboIndex = i; vboWrappers[i] = WRAPPER_FACTORY.createVboWrapper("distantHorizons:McLodRenderer");
CompletableFuture<Void> 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);
}
});
} }
} }
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. */ /** 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 variables for use in lambdas //
final int finalVboIndex = vboIndex;
final IVertexBufferWrapper finalVboWrapper = vboWrappers[vboIndex]; final IVertexBufferWrapper finalVboWrapper = vboWrappers[vboIndex];
final ByteBuffer finalVertexBuffer = vertexBuffers.get(vboIndex); final ByteBuffer finalVertexBuffer = vertexBuffers.get(vboIndex);
@@ -385,6 +340,8 @@ public class LodBufferContainer implements AutoCloseable
CompletableFuture<Void> vertexUploadFuture = new CompletableFuture<>(); CompletableFuture<Void> vertexUploadFuture = new CompletableFuture<>();
uploadFutureList.add(vertexUploadFuture); uploadFutureList.add(vertexUploadFuture);
final StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer VBO Upload", () -> RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer VBO Upload", () ->
{ {
try try
@@ -396,21 +353,12 @@ public class LodBufferContainer implements AutoCloseable
throw new InterruptedException(); throw new InterruptedException();
} }
try
{
finalVboWrapper.uploadVertexBuffer(finalVertexBuffer, finalVertexCount); finalVboWrapper.uploadVertexBuffer(finalVertexBuffer, finalVertexCount);
vertexUploadFuture.complete(null); vertexUploadFuture.complete(null);
} }
catch (Exception e) catch (Exception e)
{ {
vboWrappers[finalVboIndex] = null;
finalVboWrapper.close();
LOGGER.error("Failed to upload buffer. Error: [" + e.getMessage() + "].", e); LOGGER.error("Failed to upload buffer. Error: [" + e.getMessage() + "].", e);
}
}
catch (Exception e)
{
vertexUploadFuture.completeExceptionally(e); vertexUploadFuture.completeExceptionally(e);
} }
}); });
@@ -445,6 +393,7 @@ public class LodBufferContainer implements AutoCloseable
} }
catch (Exception e) catch (Exception e)
{ {
finalVboWrapper.close();
indexUploadFuture.completeExceptionally(e); indexUploadFuture.completeExceptionally(e);
} }
}); });
@@ -532,26 +481,29 @@ public class LodBufferContainer implements AutoCloseable
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Close", () -> RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Close", () ->
{ {
for (IVertexBufferWrapper buffer : this.vboOpaqueWrappers) tryCloseBufferWrapperArray(this.vboOpaqueWrappers);
{ tryCloseBufferWrapperArray(this.vboTransparentWrappers);
if (buffer != null)
{
buffer.close();
}
}
for (IVertexBufferWrapper buffer : this.vboTransparentWrappers)
{
if (buffer != null)
{
buffer.close();
}
}
this.uniformContainer.close(); 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 //endregion
@@ -61,7 +61,7 @@ public class LodQuadBuilder
public static final int BYTES_PER_QUAD = BYTES_PER_VERTEX * 4; public static final int BYTES_PER_QUAD = BYTES_PER_VERTEX * 4;
public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][] public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][]
///region //region
{ {
// X,Z // // X,Z //
{ // UP { // UP
@@ -109,7 +109,7 @@ public class LodQuadBuilder
{0, 0}, // 3 {0, 0}, // 3
}, },
}; };
///endregion //endregion
private int premergeCount = 0; private int premergeCount = 0;
@@ -129,6 +129,7 @@ public class FullDataToRenderDataTransformer
ColumnRenderView tempExpandingColumnView = ColumnRenderView.getPooled(); ColumnRenderView tempExpandingColumnView = ColumnRenderView.getPooled();
RenderDataPointReducingList reducingList = new RenderDataPointReducingList()) RenderDataPointReducingList reducingList = new RenderDataPointReducingList())
{ {
DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable();
for (int x = 0; x < FullDataSourceV2.WIDTH; x++) for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
{ {
for (int z = 0; z < FullDataSourceV2.WIDTH; z++) 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), baseX + BitShiftUtil.pow(x, dataDetail), baseZ + BitShiftUtil.pow(z, dataDetail),
columnArrayView, dataColumn, columnArrayView, dataColumn,
// pooled references so we don't need to re-allocate/get them 4000 times per render source // 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, ColumnRenderView columnArrayView,
LongArrayList fullDataColumn, LongArrayList fullDataColumn,
// pooled references // 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 // we can't do anything if the full data is missing or empty
if (fullDataColumn == null if (fullDataColumn == null
@@ -170,7 +171,7 @@ public class FullDataToRenderDataTransformer
if (fullDataLength <= columnArrayView.maxVerticalSliceCount) if (fullDataLength <= columnArrayView.maxVerticalSliceCount)
{ {
// Directly use the arrayView since it fits. // Directly use the arrayView since it fits.
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn); setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn, mutableBlockPos);
} }
else else
{ {
@@ -178,7 +179,7 @@ public class FullDataToRenderDataTransformer
// expand the ColumnArrayView to fit the new larger max vertical size // expand the ColumnArrayView to fit the new larger max vertical size
tempExpandingColumnView.populate(dataArrayList, fullDataLength, 0, fullDataLength); 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); columnArrayView.changeVerticalSizeFrom(tempExpandingColumnView, reducingList);
} }
@@ -186,7 +187,7 @@ public class FullDataToRenderDataTransformer
private static void setRenderColumnView( private static void setRenderColumnView(
IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource, IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource,
int blockX, int blockZ, int blockX, int blockZ,
ColumnRenderView renderColumnData, LongArrayList fullColumnData) ColumnRenderView renderColumnData, LongArrayList fullColumnData, DhBlockPosMutable mutableBlockPos)
{ {
//===============// //===============//
// config values // // config values //
@@ -242,7 +243,8 @@ public class FullDataToRenderDataTransformer
FullDataPointIdMap fullDataMapping = fullDataSource.mapping; FullDataPointIdMap fullDataMapping = fullDataSource.mapping;
DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable(blockX, 0, blockZ); mutableBlockPos.setX(blockX);
mutableBlockPos.setZ(blockZ);
// goes from the top down // goes from the top down
for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++) for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++)
@@ -450,9 +452,18 @@ public class FullDataToRenderDataTransformer
// use the previous block's color // use the previous block's color
color = colorToApplyToNextBlock; color = colorToApplyToNextBlock;
colorToApplyToNextBlock = -1; colorToApplyToNextBlock = -1;
// use the skylight override if present
if (skylightToApplyToNextBlock != -1)
{
skyLight = skylightToApplyToNextBlock; skyLight = skylightToApplyToNextBlock;
}
if (blocklightToApplyToNextBlock != -1)
{
blockLight = blocklightToApplyToNextBlock; blockLight = blocklightToApplyToNextBlock;
} }
}
@@ -29,8 +29,10 @@ import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree; import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
@@ -44,6 +46,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private final IDhClientLevel clientLevel; private final IDhClientLevel clientLevel;
@@ -106,7 +109,10 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
this.ClientRenderStateRef.set(clientRenderState); this.ClientRenderStateRef.set(clientRenderState);
} }
clientRenderState.quadtree.tryTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); // use camera position instead of player pos so free cam mods work better
Vec3d cameraDoublePos = MC_RENDER.getCameraExactPosition();
DhBlockPos2D cameraBlockPos = new DhBlockPos2D((int)cameraDoublePos.x, (int)cameraDoublePos.z);
clientRenderState.quadtree.tryTick(cameraBlockPos);
} }
@@ -170,6 +176,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
} }
this.fullDataSourceProvider.removeDataSourceUpdateListener(this); this.fullDataSourceProvider.removeDataSourceUpdateListener(this);
this.genericRenderer.close();
} }
@@ -259,7 +266,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
@Override @Override
public void close() public void close()
{ {
LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName()); //LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName());
this.quadtree.close(); this.quadtree.close();
} }
@@ -1202,17 +1202,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
{ {
this.populateListWithEnabledRenderSections(this.debugNodeList); 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++) for (int i = 0; i < this.debugNodeList.size(); i++)
{ {
LodRenderSection renderSection = this.debugNodeList.get(i); LodRenderSection renderSection = this.debugNodeList.get(i);
@@ -1264,7 +1253,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
@Override @Override
public void close() public void close()
{ {
LOGGER.info("Shutting down LodQuadTree..."); //LOGGER.info("Shutting down LodQuadTree...");
DEBUG_RENDERER.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus); DEBUG_RENDERER.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this); Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this);
@@ -1299,7 +1288,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}); });
LOGGER.info("Finished shutting down LodQuadTree"); //LOGGER.info("Finished shutting down LodQuadTree");
} }
//endregion base methods //endregion base methods
@@ -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.AbstractDebugWireframeRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer; 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.LodUtil;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
@@ -62,7 +63,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
public final long pos; public final long pos;
private final IDhClientLevel level; private final IDhClientLevel clientLevel;
private final IClientLevelWrapper levelWrapper; private final IClientLevelWrapper levelWrapper;
@WillNotClose @WillNotClose
private final FullDataSourceProviderV2 fullDataSourceProvider; private final FullDataSourceProviderV2 fullDataSourceProvider;
@@ -97,13 +98,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
*/ */
private Runnable getAndBuildRenderDataRunnable = null; private Runnable getAndBuildRenderDataRunnable = null;
/**
* Represents just uploading the {@link LodQuadBuilder} to the GPU. <br>
* 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<CompletableFuture<LodBufferContainer>> bufferUploadFutureRef = new AtomicReference<>(null);
//=============// //=============//
@@ -114,12 +108,12 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
public LodRenderSection( public LodRenderSection(
long pos, long pos,
LodQuadTree quadTree, LodQuadTree quadTree,
IDhClientLevel level, FullDataSourceProviderV2 fullDataSourceProvider) IDhClientLevel clientLevel, FullDataSourceProviderV2 fullDataSourceProvider)
{ {
this.pos = pos; this.pos = pos;
this.quadTree = quadTree; this.quadTree = quadTree;
this.level = level; this.clientLevel = clientLevel;
this.levelWrapper = level.getClientLevelWrapper(); this.levelWrapper = clientLevel.getClientLevelWrapper();
this.fullDataSourceProvider = fullDataSourceProvider; this.fullDataSourceProvider = fullDataSourceProvider;
DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus); DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
@@ -161,6 +155,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
try 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)) if (!this.getAndBuildRenderDataFutureRef.compareAndSet(null, future))
{ {
CompletableFuture<Void> oldFuture = this.getAndBuildRenderDataFutureRef.get(); CompletableFuture<Void> oldFuture = this.getAndBuildRenderDataFutureRef.get();
@@ -173,6 +169,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{ {
try try
{ {
// build LOD data on a DH thread
LodQuadBuilder lodQuadBuilder = this.getAndBuildRenderData(); LodQuadBuilder lodQuadBuilder = this.getAndBuildRenderData();
if (lodQuadBuilder == null) if (lodQuadBuilder == null)
{ {
@@ -180,7 +177,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
return; return;
} }
this.uploadToGpuAsync(lodQuadBuilder) // uploading will primarily happen on the render thread
this.uploadToGpuAsync(future, lodQuadBuilder)
.thenRun(() -> .thenRun(() ->
{ {
// the future is passed in separately (IE not using the local var) to prevent any possible race condition null pointers // 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) catch (Exception e)
{ {
LOGGER.error("Unexpected issue creating render data for pos: ["+DhSectionPos.toString(this.pos)+"], error: ["+e.getMessage()+"].", 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); executor.execute(this.getAndBuildRenderDataRunnable);
@@ -205,6 +203,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
return false; return false;
} }
} }
//=======================//
// Get LOD ID data //
// and build render data //
//=======================//
//region
@Nullable @Nullable
private synchronized LodQuadBuilder getAndBuildRenderData() private synchronized LodQuadBuilder getAndBuildRenderData()
{ {
@@ -218,7 +224,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled; 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 // get the adjacent positions
@@ -241,7 +247,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
// the render sources are only needed by this synchronous method, // the render sources are only needed by this synchronous method,
// then they can be closed // then they can be closed
ColumnRenderBufferBuilder.makeLodRenderData(lodQuadBuilder, thisRenderSource, this.level, adjacentRenderSections, adjIsSameDetailLevel); ColumnRenderBufferBuilder.makeLodRenderData(lodQuadBuilder, thisRenderSource, this.clientLevel, adjacentRenderSections, adjIsSameDetailLevel);
return lodQuadBuilder; return lodQuadBuilder;
} }
catch (Exception e) catch (Exception e)
@@ -291,53 +297,63 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
return detailLevel == DhSectionPos.getDetailLevel(this.pos); return detailLevel == DhSectionPos.getDetailLevel(this.pos);
} }
private synchronized CompletableFuture<LodBufferContainer> uploadToGpuAsync(LodQuadBuilder lodQuadBuilder)
//endregion
private synchronized CompletableFuture<LodBufferContainer> uploadToGpuAsync(
CompletableFuture<Void> parentFuture,
LodQuadBuilder lodQuadBuilder)
{ {
CompletableFuture<LodBufferContainer> oldFuture = this.bufferUploadFutureRef.getAndSet(null); CompletableFuture<LodBufferContainer> uploadFuture = LodBufferContainer.tryMakeAndUploadBuffersAsync(this.pos, this.clientLevel, lodQuadBuilder);
if (oldFuture != null) uploadFuture.whenComplete((bufferContainer, e) ->
{ {
// canceling the previous future try
// prevents the CPU from working on something that won't be used {
oldFuture.cancel(true); // 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);
} }
CompletableFuture<LodBufferContainer> future = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, this.pos, lodQuadBuilder); if (bufferContainer != null)
future.handle((lodBufferContainer, throwable) ->
{ {
if (!this.bufferUploadFutureRef.compareAndSet(future, null) // shouldn't happen, but just in case
// if the old future is canceled then the future ref will be different and that's expected bufferContainer.close();
&& !future.isCancelled() }
// if the old future is already done, then we don't care about the ref being swapped return;
&& !future.isDone())
{
LOGGER.warn("Buffer upload future ref changed for pos: ["+DhSectionPos.toString(this.pos)+"].");
} }
return null; // close the old container
}); LodBufferContainer oldContainer = this.renderBufferContainer;
this.renderBufferContainer = bufferContainer.buffersUploaded ? bufferContainer : null;
future.thenAccept((LodBufferContainer buffer) -> if (oldContainer != null)
{ {
// needed to clean up the old data oldContainer.close();
LodBufferContainer previousContainer = this.renderBufferContainer; }
// upload complete // upload complete
this.renderBufferContainer = buffer.buffersUploaded ? buffer : null;
this.renderDataDirty = false; this.renderDataDirty = false;
if (previousContainer != null)
if (parentFuture.isCancelled())
{ {
previousContainer.close(); // 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();
}
}
catch (Exception finishEx)
{
LOGGER.error("Unexpected buffer finish exception: ["+finishEx.getMessage()+"]", finishEx);
} }
}); });
return uploadFuture;
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;
} }
//endregion render data uploading //endregion render data uploading
@@ -391,8 +407,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
return; return;
} }
int levelMinY = this.level.getLevelWrapper().getMinHeight(); int levelMinY = this.clientLevel.getLevelWrapper().getMinHeight();
int levelMaxY = this.level.getLevelWrapper().getMaxHeight(); int levelMaxY = this.clientLevel.getLevelWrapper().getMaxHeight();
// show the wireframe a bit lower than world max height, // show the wireframe a bit lower than world max height,
// since most worlds don't render all the way up to the 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); // render loading is no longer needed
if (this.renderBufferContainer != null)
{
this.renderBufferContainer.close();
}
// removes any in-progress futures since they aren't needed any more
CompletableFuture<Void> buildFuture = this.getAndBuildRenderDataFutureRef.get(); CompletableFuture<Void> buildFuture = this.getAndBuildRenderDataFutureRef.get();
if (buildFuture != null) if (buildFuture != null)
{ {
@@ -451,12 +461,17 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
renderLoaderExecutor.remove(runnable); renderLoaderExecutor.remove(runnable);
} }
} }
// cancel the future after removing the runnable
// to make sure the runnable is properly removed
buildFuture.cancel(true);
} }
CompletableFuture<LodBufferContainer> uploadFuture = this.bufferUploadFutureRef.get();
if (uploadFuture != null) this.setRenderingEnabled(false);
if (this.renderBufferContainer != null)
{ {
uploadFuture.cancel(true); this.renderBufferContainer.close();
} }
} }
@@ -176,45 +176,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; return null;
} }
@@ -10,6 +10,7 @@ import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -49,6 +50,14 @@ public class RenderThreadTaskHandler
private long nanoSinceTasksRun = System.nanoTime(); 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 //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 //endregion
@@ -70,6 +94,13 @@ public class RenderThreadTaskHandler
public void queueRunningOnRenderThread(String name, Runnable renderCall) 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 // don't get the stacktrace on release to reduce GC pressure
StackTraceElement[] stackTrace = null; StackTraceElement[] stackTrace = null;
if (ModInfo.IS_DEV_BUILD) if (ModInfo.IS_DEV_BUILD)
@@ -116,12 +147,21 @@ public class RenderThreadTaskHandler
long loopStartTimeNano = System.nanoTime(); long loopStartTimeNano = System.nanoTime();
this.nanoSinceTasksRun = loopStartTimeNano; this.nanoSinceTasksRun = loopStartTimeNano;
if (this.renderThread == null)
{
this.renderThread = Thread.currentThread();
}
QueuedRunnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll(); QueuedRunnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
while(runnable != null) while(runnable != null)
{ {
long taskStartNano = System.nanoTime(); long taskStartNano = System.nanoTime();
this.currentQueuedRunnable = runnable;
runnable.run(); runnable.run();
this.currentQueuedRunnable = null;
// only try running for a limited amount of time to prevent lag spikes // only try running for a limited amount of time to prevent lag spikes
long taskNano = System.nanoTime() - taskStartNano; long taskNano = System.nanoTime() - taskStartNano;
@@ -179,8 +219,19 @@ public class RenderThreadTaskHandler
// this means we could have GL jobs building up. // this means we could have GL jobs building up.
// Run the queued tasks on MC's executor (hopefully this should always run, // Run the queued tasks on MC's executor (hopefully this should always run,
// even if DH's render code isn't being hit). // even if DH's render code isn't being hit).
IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); IMinecraftClientWrapper mcClient = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
MC.executeOnRenderThread(() -> this.runRenderThreadTasks(500 * 1_000_000L)); 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 //endregion
@@ -190,7 +241,7 @@ public class RenderThreadTaskHandler
//===========// //===========//
// debugging // // debugging //
//===========// //===========//
///region //region
/** /**
* if tasks are currently queued the debug * 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. <br>
* 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 //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 */ /** used to easily track what's being done on the render thread */
public final String name; public final String name;
@@ -26,6 +26,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; 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.DhApiRenderProxy;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.RenderParams; 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.core.wrapperInterfaces.render.renderPass.*;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import java.awt.*;
/** /**
* This is where all the magic happens. <br> * This is where all the magic happens. <br>
* This is where LODs are draw to the world. * 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 // // Apply to the MC Framebuffer //
@@ -25,6 +25,7 @@ public class ExceptionUtil
return throwable instanceof InterruptedException return throwable instanceof InterruptedException
|| throwable instanceof UncheckedInterruptedException || throwable instanceof UncheckedInterruptedException
|| throwable instanceof RejectedExecutionException || throwable instanceof RejectedExecutionException
|| throwable instanceof CancellationException
|| throwable instanceof ClosedByInterruptException; || throwable instanceof ClosedByInterruptException;
} }
@@ -37,8 +38,8 @@ public class ExceptionUtil
unwrapped instanceof CancellationException; unwrapped instanceof CancellationException;
} }
public static Throwable ensureUnwrap(Throwable t) public static Throwable ensureUnwrap(Throwable t)
{ { return t instanceof CompletionException ? ensureUnwrap(t.getCause()) : t; }
return t instanceof CompletionException ? ensureUnwrap(t.getCause()) : t;
}
} }
@@ -451,6 +451,21 @@ public class RenderDataPointReducingList extends AbstractPhantomArrayList
this.setBigger(smaller, bigger); 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.smallest = this.sortingArray.getShort(0);
this.biggest = this.sortingArray.getShort(writeIndex - 1); this.biggest = this.sortingArray.getShort(writeIndex - 1);
this.setSmaller(this.getSmallest(), NULL); this.setSmaller(this.getSmallest(), NULL);
@@ -331,7 +331,7 @@ public class PhantomArrayListPool
if (pool.logGarbageCollectedStacks if (pool.logGarbageCollectedStacks
&& checkout.allocationStackTrace != null) // stack trace shouldn't be null, but just in case && checkout.allocationStackTrace != null) // stack trace shouldn't be null, but just in case
{ {
putAndIncrementTrackingString(checkout.allocationStackTrace, allocationStackTraceCountPairList); PhantomLoggingHelper.putAndIncrementTrackingString(checkout.allocationStackTrace, allocationStackTraceCountPairList);
} }
} }
else else
@@ -363,18 +363,7 @@ public class PhantomArrayListPool
// log stack traces if present // log stack traces if present
if (pool.logGarbageCollectedStacks) if (pool.logGarbageCollectedStacks)
{ {
// high numbers first PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, allocationStackTraceCountPairList);
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());
} }
} }
} }
@@ -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<Pair<String, AtomicInteger>> 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<String, AtomicInteger> 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 ///endregion
@@ -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<Pair<String, AtomicInteger>> 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<String, AtomicInteger> 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<Pair<String, AtomicInteger>> 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<BasicPhantomReference> PHANTOM_REFERENCE_QUEUE = new ReferenceQueue<>();
private static final ConcurrentHashMap<PhantomReference<? extends BasicPhantomReference>, Class<?>> PHANTOM_TO_PARENT_CLASS = new ConcurrentHashMap<>();
private static final ThreadPoolExecutor CLEANUP_THREAD = ThreadUtil.makeSingleDaemonThreadPool("BasicPhantom Cleanup");
private final Class<?> parentClass;
private final PhantomReference<? extends BasicPhantomReference> 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<Pair<String, AtomicInteger>> allocationStackTraceCountPairList = new ArrayList<>();
ArrayList<Pair<String, AtomicInteger>> 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<? extends BasicPhantomReference> 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<? extends BasicPhantomReference>)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
}
@@ -73,10 +73,17 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor
{ {
super.afterExecute(runnable, throwable); super.afterExecute(runnable, throwable);
double ratio = this.runTimeRatioConfig.get();
if (ratio >= 1.0)
{
// Avoid sleeping for 0 time
return;
}
try try
{ {
long runTime = System.nanoTime() - this.runStartTime.get(); 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) catch (InterruptedException ignore)
{ {
@@ -23,10 +23,14 @@ import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegist
import com.seibel.distanthorizons.core.render.RenderParams; import com.seibel.distanthorizons.core.render.RenderParams;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; 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); void render(RenderParams renderEventParam, IProfilerWrapper profiler, boolean renderingWithSsao);
String getVboRenderDebugMenuString(); String getVboRenderDebugMenuString();
@Override void close(); // override to remove "throws exception"
} }
@@ -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": "distanthorizons.config.client.advanced.debugging.f3Screen":
"F3 Screen", "F3 Screen",
@@ -28,13 +28,36 @@ layout (std140) uniform fragUniformBlock
vec3 calcViewPosition(float fragmentDepth, mat4 invMvmProj) vec3 calcViewPosition(float fragmentDepth, mat4 invMvmProj)
{ {
// normalized device coordinates // normalized device coordinates
vec4 ndc = vec4(TexCoord.xy, fragmentDepth, 1.0); vec4 ndc = vec4(
TexCoord.x, // UV [0,1]
TexCoord.y,
fragmentDepth, // depth [0,1]
1.0
);
// AKA: remap the [0,1] UV coordinates and depth value
// into the [-1,1] positions used by
// the NDC cube rendering stage
ndc.xyz = ndc.xyz * 2.0 - 1.0; ndc.xyz = ndc.xyz * 2.0 - 1.0;
vec4 eyeCoord = invMvmProj * ndc; vec4 eyeCoord = invMvmProj * ndc;
return eyeCoord.xyz / eyeCoord.w; return eyeCoord.xyz / eyeCoord.w;
} }
// TODO vulkan
vec3 calcReversedZViewPosition(float fragmentDepth, mat4 invMvmProj)
{
// Vulkan NDC: xy in [-1,+1], z already in [0,1] — don't remap z
vec4 ndc = vec4(
TexCoord.x * 2.0 - 1.0, // UV [0,1] -> NDC [-1,+1]
TexCoord.y * 2.0 - 1.0,
fragmentDepth, // no remapping needed, this depth is already in the [0,1] range
1.0 // w=1 placeholder for matrix multiplication
);
vec4 eyeCoord = invMvmProj * ndc;
return eyeCoord.xyz / eyeCoord.w;
}
/** /**
* Used to fade out vanilla chunks so the transition * Used to fade out vanilla chunks so the transition
* between DH and vanilla is smoother. * between DH and vanilla is smoother.
@@ -71,12 +94,13 @@ void main()
{ {
fragColor = vec4(combinedMcDhColor.rgb, 0.0); fragColor = vec4(combinedMcDhColor.rgb, 0.0);
} }
// a fragment depth of "1" means the fragment wasn't drawn to, // // a fragment depth of "1" means the fragment wasn't drawn to,
// we only want to fade vanilla rendered objects, not to the sky or LODs // // we only want to fade vanilla rendered objects, not to the sky or LODs
else if (mcFragmentDepth < 1.0) // else if (mcFragmentDepth < 1.0)
else if (mcFragmentDepth > 0)
{ {
// fade based on distance from the camera // fade based on distance from the camera
vec3 mcVertexWorldPos = calcViewPosition(mcFragmentDepth, uMcInvMvmProj); vec3 mcVertexWorldPos = calcReversedZViewPosition(mcFragmentDepth, uMcInvMvmProj);
float mcFragmentDistance = length(mcVertexWorldPos.xzy); float mcFragmentDistance = length(mcVertexWorldPos.xzy);
@@ -1,7 +1,6 @@
#version 150 core #version 150 core
in vec2 vPosition; in vec2 vPosition;
in vec4 vColor;
out vec4 fColor; out vec4 fColor;
out vec2 TexCoord; out vec2 TexCoord;
@@ -113,12 +113,13 @@ void main()
float fadeDistance = uFadeDistanceInBlocks; float fadeDistance = uFadeDistanceInBlocks;
if (distanceFromCamera < fadeDistance) if (distanceFromCamera < fadeDistance)
{ {
#ifdef GL_ARB_derivative_control // TODO vulkan
// Get higher precision derivatives when available // #ifdef GL_ARB_derivative_control
vec3 viewNormal = cross(dFdxFine(viewPos.xyz), dFdyFine(viewPos.xyz)); // // Get higher precision derivatives when available
#else // vec3 viewNormal = cross(dFdxFine(viewPos.xyz), dFdyFine(viewPos.xyz));
// #else
vec3 viewNormal = cross(dFdx(viewPos.xyz), dFdy(viewPos.xyz)); vec3 viewNormal = cross(dFdx(viewPos.xyz), dFdy(viewPos.xyz));
#endif // #endif
viewNormal = normalize(viewNormal); viewNormal = normalize(viewNormal);
occlusion = GetSpiralOcclusion(TexCoord, viewPos, viewNormal); occlusion = GetSpiralOcclusion(TexCoord, viewPos, viewNormal);