Compare commits

..

24 Commits

Author SHA1 Message Date
s809 ad995544f7 Use bytesReceived instead of decreasing multiplicatively 2025-04-20 23:59:34 +05:00
s809 d521e931f4 Change data send tick rate 4 -> 20 2025-04-20 18:26:07 +05:00
s809 dd30a8274a Add a config entry and refactor 2025-04-20 18:25:27 +05:00
s809 3ca5efadc9 Adaptive data transfer speed 2025-04-20 03:02:18 +05:00
Ran 09174c2d2a Improve LodDataBuilder.java
- Use bitwise modulo
- Don't compute certain things 256 times when they can be computed once.
- Removed expressions that are always false
- Improved comments
2025-04-11 11:24:16 +10:00
James Seibel e079b28e77 maybe break n-sized rendering but fix LOD loading getting stuck 2025-04-07 06:56:53 -05:00
James Seibel 136124a703 up version number 2.3.2 -> 2.3.3 2025-04-05 09:11:19 -05:00
James Seibel 3ed50e5134 remove dev from version number 2025-04-05 09:10:01 -05:00
James Seibel b5e3e6867c Improve DH world gen progress message 2025-04-02 07:25:14 -05:00
James Seibel 3e04342148 Add FIXME comments to Lod and Fade renderers 2025-04-02 07:24:38 -05:00
James Seibel 6699b568df Fix memory leaks due to un-closed thread pools and worlds
How did it take this long to realize the DhWorld objects were never being closed?
2025-03-30 17:30:57 -05:00
James Seibel 53bee4ad42 Remove unused code in LodRenderer 2025-03-30 16:55:01 -05:00
James Seibel 5d5e462221 Fix the sun/moon and stars not rendering 2025-03-30 16:49:58 -05:00
James Seibel d9b924cfed Fix beacon beams now going through some blocks 2025-03-30 15:23:19 -05:00
James Seibel 8bd70d593c Fix flashing on MC 1.21.5 in non-overworld dimensions 2025-03-30 14:36:51 -05:00
James Seibel 5597044604 don't log InterruptedException during threadPool shutdown 2025-03-29 20:11:31 -05:00
James Seibel 5d7c043d06 Fix fog for MC 1.16.5 2025-03-29 19:22:51 -05:00
James Seibel 4aac61b37f hide repo double close warnings in release 2025-03-29 15:39:45 -05:00
James Seibel 22460fa1f5 Fix duplicate world gen due to short memoization time
Reverts 276f2adf00
2025-03-29 15:30:28 -05:00
James Seibel 2d127c7d98 Fix an infinite loop in the lighting engine
Not sure how I didn't catch this until MC 1.21.5
2025-03-29 15:29:34 -05:00
James Seibel 91e17c420a Fix SSAO applying to sky 2025-03-29 10:31:48 -05:00
James Seibel 93f5a85cb5 Fix MC 1.21.5 rendering and bright glass on sky 2025-03-29 10:31:34 -05:00
James Seibel b275971486 re-add stencil to GL state
shouldn't be needed, but just in case
2025-03-29 09:52:41 -05:00
James Seibel 1234ff4d28 up version number 2.3.1 -> 2.3.2 2025-03-25 07:17:27 -05:00
35 changed files with 528 additions and 237 deletions
@@ -38,7 +38,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 = "2.3.1-b";
public static final String VERSION = "2.3.3-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");
@@ -99,6 +99,11 @@ public class SharedApi
public static void setDhWorld(AbstractDhWorld newWorld)
{
AbstractDhWorld oldWorld = currentWorld;
if (oldWorld != null)
{
oldWorld.close();
}
currentWorld = newWorld;
// starting and stopping the DataRenderTransformer is necessary to prevent attempting to
@@ -1713,6 +1713,15 @@ public class Config
+ "Value of 0 disables the limit."
+ "")
.build();
public static ConfigEntry<Boolean> enableAdaptiveTransferSpeed = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "Enables adaptive transfer speed based on client performance.\n"
+ "If true, DH will automatically adjust transfer rate to minimize connection lag.\n"
+ "If false, transfer speed will remain fixed.\n"
+ "")
.build();
public static ConfigCategory experimental = new ConfigCategory.Builder().set(Experimental.class).build();
@@ -65,8 +65,6 @@ public class LodDataBuilder
// only block lighting is needed here, sky lighting is populated at the data source stage
LodUtil.assertTrue(chunkWrapper.isDhBlockLightingCorrect());
int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getX());
int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getZ());
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
@@ -80,47 +78,31 @@ public class LodDataBuilder
// compute the chunk dataSource offset
// this offset is used to determine where in the dataSource this chunk's data should go
int chunkOffsetX = chunkWrapper.getChunkPos().getX();
if (chunkWrapper.getChunkPos().getX() < 0)
{
// expected offset positions:
// chunkPos -> offset
// 5 -> 1
// 4 -> 0 ---
// 3 -> 3
// 2 -> 2
// 1 -> 1
// 0 -> 0 ===
// -1 -> 3
// -2 -> 2
// -3 -> 1
// -4 -> 0 ---
// -5 -> 3
chunkOffsetX = ((chunkOffsetX) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
if (chunkOffsetX != 0)
{
chunkOffsetX += FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
}
}
else
{
chunkOffsetX %= FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
}
chunkOffsetX *= LodUtil.CHUNK_WIDTH;
int chunkOffsetZ = chunkWrapper.getChunkPos().getZ();
if (chunkWrapper.getChunkPos().getZ() < 0)
{
chunkOffsetZ = ((chunkOffsetZ) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
if (chunkOffsetZ != 0)
{
chunkOffsetZ += FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
}
}
else
{
chunkOffsetZ %= FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
}
// expected offset positions:
// chunkPos -> offset
// 5 -> 1
// 4 -> 0 ---
// 3 -> 3
// 2 -> 2
// 1 -> 1
// 0 -> 0 ===
// -1 -> 3
// -2 -> 2
// -3 -> 1
// -4 -> 0 ---
// -5 -> 3
// Fast modulo calculation using bitwise AND since NUMB_OF_CHUNKS_WIDE is a power of 2 (4)
// For any number n: n & (2^k - 1) is equivalent to Math.floorMod(n, 2^k)
// Original: Math.floorMod(x, 4) - Handles negative numbers, gives non-negative result in range [0,3]
// Bitwise: x & (4-1) - Also gives non-negative result in range [0,3]
// Example: -5 & 3 = 3, which equals Math.floorMod(-5, 4) = 3
int chunkOffsetX = chunkWrapper.getChunkPos().getX() & (FullDataSourceV2.NUMB_OF_CHUNKS_WIDE - 1);
int chunkOffsetZ = chunkWrapper.getChunkPos().getZ() & (FullDataSourceV2.NUMB_OF_CHUNKS_WIDE - 1);
// Convert from chunk coordinates to block coordinates
chunkOffsetX *= LodUtil.CHUNK_WIDTH;
chunkOffsetZ *= LodUtil.CHUNK_WIDTH;
@@ -138,54 +120,49 @@ public class LodDataBuilder
IBlockStateWrapper previousBlockState = null;
int minBuildHeight = chunkWrapper.getMinNonEmptyHeight();
int exclusiveMaxBuildHeight = chunkWrapper.getExclusiveMaxBuildHeight();
int inclusiveMinBuildHeight = chunkWrapper.getInclusiveMinBuildHeight();
int dataCapacity = chunkWrapper.getHeight() / 4;
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
{
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
{
LongArrayList longs = dataSource.get(
relBlockX + chunkOffsetX,
relBlockZ + chunkOffsetZ);
// Calculate column position
int columnX = relBlockX + chunkOffsetX;
int columnZ = relBlockZ + chunkOffsetZ;
// Get column data
LongArrayList longs = dataSource.get(columnX, columnZ);
if (longs == null)
{
longs = new LongArrayList(chunkWrapper.getHeight() / 4);
longs = new LongArrayList(dataCapacity);
}
else
{
longs.clear();
}
int lastY = chunkWrapper.getExclusiveMaxBuildHeight();
int lastY = exclusiveMaxBuildHeight;
IBiomeWrapper biome = chunkWrapper.getBiome(relBlockX, lastY, relBlockZ);
IBlockStateWrapper blockState = AIR;
int mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
// Determine lighting (we are at the height limit. There are no torches here, and sky is not obscured.) // TODO: Per face lighting someday?
byte blockLight = LodUtil.MIN_MC_LIGHT;
byte skyLight = LodUtil.MAX_MC_LIGHT;
byte blockLight;
byte skyLight;
if (lastY < chunkWrapper.getExclusiveMaxBuildHeight())
{
// FIXME: The lastY +1 offset is to reproduce the old behavior. Remove this when we get per-face lighting
blockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, lastY + 1, relBlockZ);
skyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, lastY + 1, relBlockZ);
}
else
{
//we are at the height limit. There are no torches here, and sky is not obscured.
blockLight = LodUtil.MIN_MC_LIGHT;
skyLight = LodUtil.MAX_MC_LIGHT;
}
// determine the starting Y Pos
// Get the maximum height from both heightmaps
int y = Math.max(
// max between both heightmaps to account for solid invisible blocks (glass)
// and non-solid opaque blocks (at one point this was stairs, not sure what would fit this now)
chunkWrapper.getLightBlockingHeightMapValue(relBlockX, relBlockZ),
chunkWrapper.getSolidHeightMapValue(relBlockX, relBlockZ)
);
// go up until we reach open air or the world limit
);
// Go up until we reach open air or the world limit
IBlockStateWrapper topBlockState = previousBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ, mcBlockPos, previousBlockState);
while (!topBlockState.isAir() && y < chunkWrapper.getExclusiveMaxBuildHeight())
while (!topBlockState.isAir() && y < exclusiveMaxBuildHeight)
{
try
{
@@ -198,7 +175,7 @@ public class LodDataBuilder
{
if (!getTopErrorLogged)
{
LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + chunkWrapper.getExclusiveMaxBuildHeight() + "] had issue getting block at pos [" + relBlockX + "," + y + "," + relBlockZ + "] error: " + e.getMessage(), e);
LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + exclusiveMaxBuildHeight + "] had issue getting block at pos [" + relBlockX + "," + y + "," + relBlockZ + "] error: " + e.getMessage(), e);
getTopErrorLogged = true;
}
@@ -207,7 +184,7 @@ public class LodDataBuilder
}
}
// Process blocks from top to bottom
for (; y >= minBuildHeight; y--)
{
IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ);
@@ -215,10 +192,10 @@ public class LodDataBuilder
byte newBlockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, y + 1, relBlockZ);
byte newSkyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, y + 1, relBlockZ);
// save the biome/block change
// Save the biome/block change if different from previous
if (!newBiome.equals(biome) || !newBlockState.equals(blockState))
{
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getInclusiveMinBuildHeight(), blockLight, skyLight));
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - inclusiveMinBuildHeight, blockLight, skyLight));
biome = newBiome;
blockState = newBlockState;
@@ -228,17 +205,16 @@ public class LodDataBuilder
lastY = y;
}
}
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getInclusiveMinBuildHeight(), blockLight, skyLight));
dataSource.setSingleColumn(longs,
relBlockX + chunkOffsetX,
relBlockZ + chunkOffsetZ,
EDhApiWorldGenerationStep.LIGHT,
worldCompressionMode);
// Add the final data point
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - inclusiveMinBuildHeight, blockLight, skyLight));
// Set the column in the data source
dataSource.setSingleColumn(longs, columnX, columnZ, EDhApiWorldGenerationStep.LIGHT, worldCompressionMode);
}
}
if (ignoreHiddenBlocks)
if (ignoreHiddenBlocks)
{
cullHiddenBlocks(dataSource, chunkOffsetX, chunkOffsetZ);
}
@@ -275,16 +251,16 @@ public class LodDataBuilder
{
long currentPoint = centerColumn.getLong(centerIndex);
// translucent data points are not eligible to be culled.
// Translucent data points are not eligible to be culled.
if (isTranslucent(dataSource, currentPoint))
{
continue;
}
// the top segment should never be culled.
if (centerIndex == 0
|| isTranslucent(dataSource, centerColumn.getLong(centerIndex - 1))
)
if (centerIndex == 0
|| isTranslucent(dataSource, centerColumn.getLong(centerIndex - 1))
)
{
continue;
}
@@ -292,9 +268,9 @@ public class LodDataBuilder
// the bottom segment can sometimes be culled.
// assume it will not be seen from below,
// because this would imply the player is in the void.
if (centerIndex + 1 < centerColumn.size()
&& isTranslucent(dataSource, centerColumn.getLong(centerIndex + 1))
)
if (centerIndex + 1 < centerColumn.size()
&& isTranslucent(dataSource, centerColumn.getLong(centerIndex + 1))
)
{
continue;
}
@@ -327,9 +303,11 @@ public class LodDataBuilder
continue;
}
// current point is fully surrounded. remove it.
// Current point is fully surrounded. remove it.
centerColumn.removeLong(centerIndex);
// make the above data point cover the area that the current point used to occupy.
// Make the above data point cover the area that the current point used to occupy.
// The element that was at `centerIndex - 1` is still at that position even after removal of centerIndex.
long above = centerColumn.getLong(centerIndex - 1);
above = FullDataPointUtil.setBottomY(above, FullDataPointUtil.getBottomY(currentPoint));
above = FullDataPointUtil.setHeight(above, FullDataPointUtil.getHeight(currentPoint) + FullDataPointUtil.getHeight(above));
@@ -338,31 +316,31 @@ public class LodDataBuilder
}
}
}
/**
checks if centerPoint is "covered" by opaque data points in adjacentColumn.
centerPoint counts as covered if, and only if, for all Y levels in its height range,
there exists an opaque data point in adjacentColumn which overlaps with that Y level.
@param source used to lookup blocks (and their opacities) based on their IDs.
@param centerPoint the point being checked to see if it's fully covered.
@param adjacentColumn the data points which might cover centerPoint.
@param adjacentIndex the starting index in adjacentColumn to start scanning at.
indices greater than adjacentIndex have already been checked and confirmed to
not overlap or only overlap partially with centerPoint's Y range.
@return if centerPoint is covered, returns the index of the segment which finishes covering it.
the start of the covering may be a smaller index. in this case, the returned index may be used
as the adjacentIndex provided to this method on the next iteration which yields a new centerPoint.
if centerPoint is NOT covered, returns the bitwise negation of the index of the
segment which did not cover it. this guarantees that the returned value is negative.
the caller should check for negative return values and manually un-negate them to proceed with the loop.
in other words, this function returns the index of the next adjacent data
point to use in the loop, AND a boolean indicating whether or not the
centerPoint is covered; both are packed into the same int, and returned.
*/
checks if centerPoint is "covered" by opaque data points in adjacentColumn.
centerPoint counts as covered if, and only if, for all Y levels in its height range,
there exists an opaque data point in adjacentColumn which overlaps with that Y level.
@param source used to lookup blocks (and their opacities) based on their IDs.
@param centerPoint the point being checked to see if it's fully covered.
@param adjacentColumn the data points which might cover centerPoint.
@param adjacentIndex the starting index in adjacentColumn to start scanning at.
indices greater than adjacentIndex have already been checked and confirmed to
not overlap or only overlap partially with centerPoint's Y range.
@return if centerPoint is covered, returns the index of the segment which finishes covering it.
the start of the covering may be a smaller index. in this case, the returned index may be used
as the adjacentIndex provided to this method on the next iteration which yields a new centerPoint.
if centerPoint is NOT covered, returns the bitwise negation of the index of the
segment which did not cover it. this guarantees that the returned value is negative.
the caller should check for negative return values and manually un-negate them to proceed with the loop.
in other words, this function returns the index of the next adjacent data
point to use in the loop, AND a boolean indicating whether or not the
centerPoint is covered; both are packed into the same int, and returned.
*/
private static int checkOcclusion(FullDataSourceV2 source, long centerPoint, LongArrayList adjacentColumn, int adjacentIndex)
{
int bottomOfCenter = FullDataPointUtil.getBottomY(centerPoint);
@@ -387,12 +365,12 @@ public class LodDataBuilder
throw new LodUtil.AssertFailureException("Adjacent column ends before center column does.");
}
private static boolean isTranslucent(FullDataSourceV2 source, long point) {
return source.mapping.getBlockStateWrapper(FullDataPointUtil.getId(point)).getOpacity() < LodUtil.BLOCK_FULLY_OPAQUE;
}
/** @throws ClassCastException if an API user returns the wrong object type(s) */
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk apiChunk, boolean runAdditionalValidation) throws ClassCastException, DataCorruptedException, IllegalArgumentException
@@ -402,9 +380,13 @@ public class LodDataBuilder
int sectionPosZ = getXOrZSectionPosFromChunkPos(apiChunk.chunkPosZ);
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
// chunk relative block position in the data source
int relSourceBlockX = Math.floorMod(apiChunk.chunkPosX, 4) * LodUtil.CHUNK_WIDTH;
int relSourceBlockZ = Math.floorMod(apiChunk.chunkPosZ, 4) * LodUtil.CHUNK_WIDTH;
// Fast modulo calculation using bitwise AND since NUMB_OF_CHUNKS_WIDE is a power of 2 (4)
// For any number n: n & (2^k - 1) is equivalent to Math.floorMod(n, 2^k)
// Original: Math.floorMod(x, 4) - Handles negative numbers, gives non-negative result in range [0,3]
// Bitwise: x & (4-1) - Also gives non-negative result in range [0,3]
// Example: -5 & 3 = 3, which equals Math.floorMod(-5, 4) = 3
int relSourceBlockX = (apiChunk.chunkPosX & (FullDataSourceV2.NUMB_OF_CHUNKS_WIDE - 1)) * LodUtil.CHUNK_WIDTH;
int relSourceBlockZ = (apiChunk.chunkPosZ & (FullDataSourceV2.NUMB_OF_CHUNKS_WIDE - 1)) * LodUtil.CHUNK_WIDTH;
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
@@ -423,8 +405,8 @@ public class LodDataBuilder
// TODO add the ability for API users to define a different compression mode
// or add a "unkown" compression mode
dataSource.setSingleColumn(
packedDataPoints,
relBlockX + relSourceBlockX, relBlockZ + relSourceBlockZ,
packedDataPoints,
relBlockX + relSourceBlockX, relBlockZ + relSourceBlockZ,
EDhApiWorldGenerationStep.LIGHT, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
dataSource.isEmpty = false;
}
@@ -440,7 +422,7 @@ public class LodDataBuilder
/** @see FullDataPointUtil */
public static LongArrayList convertApiDataPointListToPackedLongArray(
@Nullable List<DhApiTerrainDataPoint> columnDataPoints, FullDataSourceV2 dataSource,
@Nullable List<DhApiTerrainDataPoint> columnDataPoints, FullDataSourceV2 dataSource,
int bottomYBlockPos) throws DataCorruptedException
{
// this null check does 2 nice things at the same time:
@@ -533,13 +515,13 @@ public class LodDataBuilder
}
// is there a gap between the last datapoint?
if (topYPos != lastBottomYPos
&& lastBottomYPos != Integer.MIN_VALUE)
&& lastBottomYPos != Integer.MIN_VALUE)
{
throw new IllegalArgumentException("DhApiTerrainDataPoint ["+i+"] has a gap between it and index ["+(i-1)+"]. Empty spaces should be filled by air, otherwise DH's downsampling won't calculate lighting correctly.");
}
lastBottomYPos = bottomYPos;
lastBottomYPos = bottomYPos;
}
}
@@ -328,7 +328,8 @@ public class DhLightingEngine
continue;
}
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight() || relNeighbourBlockPos.getY() > neighbourChunk.getExclusiveMaxBuildHeight())
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight()
|| relNeighbourBlockPos.getY() >= neighbourChunk.getExclusiveMaxBuildHeight())
{
// the light pos is outside the chunk's min/max height,
// this can happen if given a chunk that hasn't finished generating
@@ -290,7 +290,7 @@ public class WorldGenModule implements Closeable
remainingChunkCount += this.worldGenerationQueue.getQueuedChunkCount();
String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount);
String message = "DH Gen/Import: " + remainingChunkCountStr + " chunks left.";
String message = "DH is loading chunks. " + remainingChunkCountStr + " left.";
// show a message about how to disable progress logging if requested
int msToShowDisableInstructions = Config.Common.WorldGenerator.generationProgressDisableMessageDisplayTimeInSeconds.get() * 1_000;
@@ -0,0 +1,71 @@
package com.seibel.distanthorizons.core.multiplayer.client;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BooleanSupplier;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
public class ClientCongestionControl
{
private static final double ADDITIVE_INCREASE = 50000;
private static final long INTERVAL_MS = 1000;
private final Runnable rateUpdateHandler;
private final AtomicLong bytesReceived = new AtomicLong(0);
private double desiredRate;
private long lastAdjustTime;
public ClientCongestionControl(
Runnable rateUpdateHandler
)
{
this.rateUpdateHandler = rateUpdateHandler;
this.reset();
}
public void reset()
{
this.desiredRate = ADDITIVE_INCREASE;
this.lastAdjustTime = System.currentTimeMillis();
this.bytesReceived.set(0);
}
public void onPayloadReceived(FullDataSplitMessage message)
{
long now = System.currentTimeMillis();
if (now - this.lastAdjustTime >= INTERVAL_MS)
{
this.adjustRate(now);
}
this.bytesReceived.addAndGet(message.buffer.readableBytes());
}
private void adjustRate(long now)
{
double throughput = this.bytesReceived.getAndSet(0);
if (throughput >= this.desiredRate)
{
this.desiredRate += ADDITIVE_INCREASE;
}
else
{
this.desiredRate = Math.max(throughput - ADDITIVE_INCREASE / 2, 1000);
}
this.lastAdjustTime = now;
this.rateUpdateHandler.run();
}
public int getDesiredRate()
{
return (int) (this.desiredRate / 1000);
}
}
@@ -1,6 +1,7 @@
package com.seibel.distanthorizons.core.multiplayer.client;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
@@ -56,6 +57,23 @@ public class ClientNetworkState implements Closeable
private long serverTimeOffset = 0;
public long getServerTimeOffset() { return this.serverTimeOffset; }
private final ClientCongestionControl congestionControl = new ClientCongestionControl(
() -> {
if (Config.Server.enableAdaptiveTransferSpeed.get())
{
this.sendConfigMessage(false);
}
}
);
private final ConfigChangeListener<Boolean> adaptiveTransferSpeedListener = new ConfigChangeListener<>(Config.Server.enableAdaptiveTransferSpeed, isEnabled -> {
if (isEnabled)
{
this.congestionControl.reset();
}
this.sendConfigMessage();
});
//=============//
@@ -116,6 +134,7 @@ public class ClientNetworkState implements Closeable
});
this.networkSession.registerHandler(FullDataSplitMessage.class, this.fullDataPayloadReceiver::receiveChunk);
this.networkSession.registerHandler(FullDataSplitMessage.class, this.congestionControl::onPayloadReceived);
}
}
@@ -127,10 +146,22 @@ public class ClientNetworkState implements Closeable
public void sendConfigMessage()
public void sendConfigMessage() { this.sendConfigMessage(true); }
public void sendConfigMessage(boolean blocking)
{
this.configReceived = false;
this.getSession().sendMessage(new SessionConfigMessage(new SessionConfig()));
SessionConfig sessionConfig = new SessionConfig();
if (Config.Server.enableAdaptiveTransferSpeed.get())
{
sessionConfig.constrainValue(Config.Server.maxDataTransferSpeed, this.congestionControl.getDesiredRate());
}
if (blocking)
{
this.configReceived = false;
}
this.getSession().sendMessage(new SessionConfigMessage(sessionConfig));
}
@@ -166,6 +197,7 @@ public class ClientNetworkState implements Closeable
public void close()
{
this.fullDataPayloadReceiver.close();
this.adaptiveTransferSpeedListener.close();
this.configAnyChangeListener.close();
this.networkSession.close();
}
@@ -18,7 +18,7 @@ public class SessionConfig implements INetworkObject
private static final LinkedHashMap<String, Entry> CONFIG_ENTRIES = new LinkedHashMap<>();
private final LinkedHashMap<String, Object> values = new LinkedHashMap<>();
private final HashMap<String, Object> values = new HashMap<>();
public SessionConfig constrainingConfig;
@@ -123,6 +123,13 @@ public class SessionConfig implements INetworkObject
: value);
}
public <T> void constrainValue(ConfigEntry<T> configEntry, T value) { this.constrainValue(configEntry.getChatCommandName(), value); }
private void constrainValue(String name, Object value)
{
Entry entry = CONFIG_ENTRIES.get(name);
this.values.put(name, entry.valueConstrainer.apply(this.getValue(name), value));
}
private Map<String, ?> getValues()
{
return CONFIG_ENTRIES.keySet().stream().collect(Collectors.toMap(
@@ -34,8 +34,6 @@ public class FullDataPayloadReceiver implements AutoCloseable
})
.build().asMap();
@Override
public void close()
{
@@ -13,7 +13,7 @@ import java.util.function.*;
public class FullDataPayloadSender implements AutoCloseable
{
private static final int TICK_RATE = 4;
private static final int TICK_RATE = 20;
/** 1 Mebibyte minus 576 bytes for other info */
public static final int FULL_DATA_SPLIT_SIZE_IN_BYTES = 1_048_000;
@@ -399,9 +399,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// prepare this section for rendering
if (!renderSection.gpuUploadInProgress()
&& renderSection.renderBuffer == null
// TODO this is commented out since some users reported LODs refusing to
// load at their expected higher-detail levels
// this check is specifically for N-sized world generators where the higher quality
// data source may not exist yet, this is done to prevent holes while waiting for said generator
&& renderSection.getFullDataSourceExists()
//&& renderSection.getFullDataSourceExists()
)
{
nodesNeedingLoading.add(renderSection);
@@ -510,10 +510,13 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{
// TODO memoization is needed for multiplayer, otherwise
// new retrieval requests won't be submitted.
// TODO why is that the case? Shouldn't the missing positions be un-changing?
// TODO why is that the case? Shouldn't the missing positions be un-changing?
// TODO setting this value to low can cause world gen to slow down significantly
// due to a race condition where the world gen thinks it is finished, but the results
// haven't been saved to file yet, causing the gen to fire again
this.missingGenerationPosFunc = Suppliers.memoizeWithExpiration(
() -> this.fullDataSourceProvider.getPositionsToRetrieve(this.pos),
15, TimeUnit.SECONDS);
10, TimeUnit.MINUTES);
}
LongArrayList missingGenerationPos = this.getMissingGenerationPos();
@@ -359,6 +359,7 @@ public class RenderBufferHandler implements AutoCloseable
// debug wireframe setup //
//=======================//
// TODO move this logic into LodRenderer so all the GL states can be handled there
boolean renderWireframe = Config.Client.Advanced.Debugging.renderWireframe.get();
if (renderWireframe)
{
@@ -57,10 +57,10 @@ public class GLState
public boolean depth;
public boolean writeToDepthBuffer;
public int depthFunc;
//public boolean stencil;
//public int stencilFunc;
//public int stencilRef;
//public int stencilMask;
public boolean stencil;
public int stencilFunc;
public int stencilRef;
public int stencilMask;
public int[] view;
public boolean cull;
public int cullMode;
@@ -121,10 +121,10 @@ public class GLState
this.depth = GL32.glIsEnabled(GL32.GL_DEPTH_TEST);
this.writeToDepthBuffer = GL32.glGetInteger(GL32.GL_DEPTH_WRITEMASK) == GL32.GL_TRUE;
this.depthFunc = GL32.glGetInteger(GL32.GL_DEPTH_FUNC);
//this.stencil = GL32.glIsEnabled(GL32.GL_STENCIL_TEST);
//this.stencilFunc = GL32.glGetInteger(GL32.GL_STENCIL_FUNC);
//this.stencilRef = GL32.glGetInteger(GL32.GL_STENCIL_REF);
//this.stencilMask = GL32.glGetInteger(GL32.GL_STENCIL_VALUE_MASK);
this.stencil = GL32.glIsEnabled(GL32.GL_STENCIL_TEST);
this.stencilFunc = GL32.glGetInteger(GL32.GL_STENCIL_FUNC);
this.stencilRef = GL32.glGetInteger(GL32.GL_STENCIL_REF);
this.stencilMask = GL32.glGetInteger(GL32.GL_STENCIL_VALUE_MASK);
this.view = new int[4];
GL32.glGetIntegerv(GL32.GL_VIEWPORT, this.view);
this.cull = GL32.glIsEnabled(GL32.GL_CULL_FACE);
@@ -143,11 +143,11 @@ public class GLState
", FB depth=" + this.frameBufferDepthTexture +
", blend=" + this.blend + ", scissor=" + this.scissor + ", blendMode=" + GLEnums.getString(this.blendSrcColor) + "," + GLEnums.getString(this.blendDstColor) +
", depth=" + this.depth +
//", depthFunc=" + GLEnums.getString(this.depthFunc) + ", stencil=" + this.stencil + ", stencilFunc=" +
//GLEnums.getString(this.stencilFunc) + ", stencilRef=" + this.stencilRef + ", stencilMask=" + this.stencilMask +
", depthFunc=" + GLEnums.getString(this.depthFunc) + ", stencil=" + this.stencil +
", stencilFunc=" + GLEnums.getString(this.stencilFunc) + ", stencilRef=" + this.stencilRef + ", stencilMask=" + this.stencilMask +
", view={x:" + this.view[0] + ", y:" + this.view[1] +
", w:" + this.view[2] + ", h:" + this.view[3] + "}" + ", cull=" + this.cull + ", cullMode="
+ GLEnums.getString(this.cullMode) + ", polyMode=" + GLEnums.getString(this.polyMode) +
", w:" + this.view[2] + ", h:" + this.view[3] + "}" + ", cull=" + this.cull +
", cullMode=" + GLEnums.getString(this.cullMode) + ", polyMode=" + GLEnums.getString(this.polyMode) +
'}';
}
@@ -233,15 +233,15 @@ public class GLState
}
GLMC.glDepthFunc(this.depthFunc);
//if (this.stencil)
//{
// GL32.glEnable(GL32.GL_STENCIL_TEST);
//}
//else
//{
// GL32.glDisable(GL32.GL_STENCIL_TEST);
//}
//GL32.glStencilFunc(this.stencilFunc, this.stencilRef, this.stencilMask);
if (this.stencil)
{
GL32.glEnable(GL32.GL_STENCIL_TEST);
}
else
{
GL32.glDisable(GL32.GL_STENCIL_TEST);
}
GL32.glStencilFunc(this.stencilFunc, this.stencilRef, this.stencilMask);
GL32.glViewport(this.view[0], this.view[1], this.view[2], this.view[3]);
if (this.cull)
@@ -86,22 +86,31 @@ public class FadeRenderer
this.fadeFramebuffer = -1;
}
if (this.fadeTexture != -1)
{
GLMC.glDeleteTextures(this.fadeTexture);
this.fadeTexture = -1;
}
this.fadeFramebuffer = GL32.glGenFramebuffers();
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fadeFramebuffer);
this.fadeTexture = GL32.glGenTextures();
GLMC.glBindTexture(this.fadeTexture);
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fadeTexture, 0);
// Applying the fade texture is only needed if MC is drawing to their own frame buffer,
// otherwise we can directly render to their texture
if (MC_RENDER.mcRendersToFrameBuffer())
{
if (this.fadeTexture != -1)
{
GLMC.glDeleteTextures(this.fadeTexture);
this.fadeTexture = -1;
}
this.fadeTexture = GL32.glGenTextures();
GLMC.glBindTexture(this.fadeTexture);
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fadeTexture, 0);
}
else
{
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, MC_RENDER.getColorTextureId(), 0);
}
}
@@ -146,8 +155,13 @@ public class FadeRenderer
profiler.popPush("Fade Apply");
FadeApplyShader.INSTANCE.fadeTexture = this.fadeTexture;
FadeApplyShader.INSTANCE.render(partialTicks);
// Applying the fade texture is only needed if MC is drawing to their own frame buffer,
// otherwise we can directly render to their texture
if (MC_RENDER.mcRendersToFrameBuffer())
{
FadeApplyShader.INSTANCE.fadeTexture = this.fadeTexture;
FadeApplyShader.INSTANCE.render(partialTicks);
}
profiler.pop();
}
@@ -557,34 +557,24 @@ public class LodRenderer
activeFrameBuffer.bind();
boolean clearTextures = !ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeTextureClearEvent.class, renderEventParam);
if (clearTextures)
{
if (this.usingMcFrameBuffer && framebufferOverride == null)
{
// Due to using MC/Optifine's framebuffer we need to re-bind the depth texture,
// otherwise we'll be writing to MC/Optifine's depth texture which causes rendering issues
activeFrameBuffer.addDepthAttachment(this.depthTexture.getTextureId(), EDhDepthBufferFormat.DEPTH32F.isCombinedStencil());
// don't clear the color texture, that removes the sky
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
}
else if (firstPass)
{
GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT);
}
}
// by default draw everything as triangles
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
GLMC.enableFaceCulling();
GLMC.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA);
GLMC.glBlendFuncSeparate(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA, GL32.GL_ONE, GL32.GL_ZERO);
GL32.glDisable(GL32.GL_SCISSOR_TEST);
// Enable depth test and depth mask
GLMC.enableDepthTest();
GLMC.glDepthFunc(GL32.GL_LESS);
GLMC.enableDepthMask();
// This is required for MC versions 1.21.5+
// due to MC updating the lightmap by changing the viewport size
GL32.glViewport(0, 0, this.cachedWidth, this.cachedHeight);
/*---------Bind required objects--------*/
// Setup LodRenderProgram and the LightmapTexture if it has not yet been done
// also binds LightmapTexture, VAO, and ShaderProgram
@@ -605,6 +595,33 @@ public class LodRenderer
}
this.lodRenderProgram.fillUniformData(renderEventParam);
// needs to be fired after all the textures have been created/bound
boolean clearTextures = !ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeTextureClearEvent.class, renderEventParam);
if (clearTextures)
{
GL32.glClearDepth(1.0);
float[] clearColorValues = new float[4];
GL32.glGetFloatv(GL32.GL_COLOR_CLEAR_VALUE, clearColorValues);
GL32.glClearColor(clearColorValues[0], clearColorValues[1], clearColorValues[2], 1.0f);
if (this.usingMcFrameBuffer && framebufferOverride == null)
{
// Due to using MC/Optifine's framebuffer we need to re-bind the depth texture,
// otherwise we'll be writing to MC/Optifine's depth texture which causes rendering issues
activeFrameBuffer.addDepthAttachment(this.depthTexture.getTextureId(), EDhDepthBufferFormat.DEPTH32F.isCombinedStencil());
// don't clear the color texture, that removes the sky
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
}
else if (firstPass)
{
GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT);
}
}
}
/** Setup all render objects - MUST be called on the render thread */
@@ -656,7 +673,7 @@ public class LodRenderer
if(this.framebuffer.getStatus() != GL32.GL_FRAMEBUFFER_COMPLETE)
{
// This generally means something wasn't bound, IE missing either the color or depth texture
SPAM_LOGGER.warn("FrameBuffer ["+this.framebuffer.getId()+"] isn't complete.");
EVENT_LOGGER.warn("FrameBuffer ["+this.framebuffer.getId()+"] isn't complete.");
}
@@ -716,25 +733,6 @@ public class LodRenderer
private Color getFogColor(float partialTicks)
{
Color fogColor;
if (Config.Client.Advanced.Graphics.Fog.colorMode.get() == EDhApiFogColorMode.USE_SKY_COLOR)
{
fogColor = MC_RENDER.getSkyColor();
}
else
{
fogColor = MC_RENDER.getFogColor(partialTicks);
}
return fogColor;
}
private Color getSpecialFogColor(float partialTicks) { return MC_RENDER.getSpecialFogColor(partialTicks); }
//===============//
// API functions //
//===============//
@@ -748,7 +746,10 @@ public class LodRenderer
public static int getActiveColorTextureId() { return activeColorTextureId; }
private void setActiveDepthTextureId(int depthTextureId) { activeDepthTextureId = depthTextureId; }
/** Returns -1 if no texture has been bound yet */
/**
* FIXME it's possible for this to return an invalid texture ID if the renderer is being re-built at the same time
* Returns -1 if no texture has been bound yet
*/
public static int getActiveDepthTextureId() { return activeDepthTextureId; }
@@ -75,6 +75,18 @@ public class DhApplyShader extends AbstractShaderRenderer
@Override
protected void onRender()
{
if (MC_RENDER.mcRendersToFrameBuffer())
{
this.renderToFrameBuffer();
}
else
{
this.renderToMcTexture();
}
}
// TODO merge duplicate code between these to render methods
private void renderToFrameBuffer()
{
int targetFrameBuffer = MC_RENDER.getTargetFrameBuffer();
if (targetFrameBuffer == -1)
@@ -87,9 +99,15 @@ public class DhApplyShader extends AbstractShaderRenderer
GLMC.disableDepthTest();
GLMC.enableBlend();
GL32.glBlendEquation(GL32.GL_FUNC_ADD);
GLMC.glBlendFunc(GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA);
// blending isn't needed, we're manually merging the MC and DH textures
// Note: this prevents the sun/moon and stars from rendering through transparent LODs,
// however this also fixes transparent LODs from glowing when rendered against the sky during the day
GLMC.disableBlend();
// old blending logic in case it's ever needed:
//GLMC.enableBlend();
//GL32.glBlendEquation(GL32.GL_FUNC_ADD);
//GLMC.glBlendFunc(GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA);
GLMC.glActiveTexture(GL32.GL_TEXTURE0);
GLMC.glBindTexture(LodRenderer.getActiveColorTextureId());
@@ -110,5 +128,66 @@ public class DhApplyShader extends AbstractShaderRenderer
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, targetFrameBuffer);
}
private void renderToMcTexture()
{
int targetColorTextureId = MC_RENDER.getColorTextureId();
if (targetColorTextureId == -1)
{
return;
}
int dhFrameBufferId = LodRenderer.getActiveFramebufferId();
if (dhFrameBufferId == -1)
{
return;
}
int mcFrameBufferId = MC_RENDER.getTargetFrameBuffer();
if (mcFrameBufferId == -1)
{
return;
}
GLState state = new GLState();
GLMC.disableDepthTest();
// blending isn't needed, we're just directly merging the MC and DH textures
// Note: this prevents the sun/moon and stars from rendering through transparent LODs,
// however this also fixes
GLMC.disableBlend();
// old blending logic in case it's ever needed:
//GLMC.enableBlend();
//GL32.glBlendEquation(GL32.GL_FUNC_ADD);
//GLMC.glBlendFunc(GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA);
GLMC.glActiveTexture(GL32.GL_TEXTURE0);
GLMC.glBindTexture(LodRenderer.getActiveColorTextureId());
GL32.glUniform1i(this.gDhColorTextureUniform, 0);
GLMC.glActiveTexture(GL32.GL_TEXTURE1);
GLMC.glBindTexture(LodRenderer.getActiveDepthTextureId());
GL32.glUniform1i(this.gDepthMapUniform, 1);
GL32.glFramebufferTexture(GL32.GL_DRAW_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, targetColorTextureId, 0);
// Copy to MC's texture via MC's framebuffer
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, dhFrameBufferId);
ScreenQuad.INSTANCE.render();
// restore everything, except at this point the MC framebuffer should now be used instead
state.restore();
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, mcFrameBufferId);
}
}
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.render.renderer.shaders;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.render.glObject.GLState;
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
import com.seibel.distanthorizons.core.render.renderer.FadeRenderer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
@@ -104,6 +105,12 @@ public class FadeApplyShader extends AbstractShaderRenderer
@Override
protected void onRender()
{
if (!MC_RENDER.mcRendersToFrameBuffer())
{
throw new IllegalStateException("If Minecraft is directly rendering to a texture the apply shader isn't needed, just draw the fade directly to the MC color texture.");
}
GLMC.disableBlend();
// Depth testing must be disabled otherwise this application shader won't apply anything.
@@ -119,6 +126,9 @@ public class FadeApplyShader extends AbstractShaderRenderer
ScreenQuad.INSTANCE.render();
GLMC.enableDepthTest();
}
}
@@ -165,6 +165,7 @@ public class FadeShader extends AbstractShaderRenderer
GL32.glUniform1i(this.uMcDepthTexture, 0);
GLMC.glActiveTexture(GL32.GL_TEXTURE1);
// FIXME it's possible for this to return an invalid texture ID if the renderer is being re-built at the same time
GLMC.glBindTexture(LodRenderer.getActiveDepthTextureId());
GL32.glUniform1i(this.uDhDepthTexture, 1);
@@ -84,6 +84,7 @@ public class FogApplyShader extends AbstractShaderRenderer
GLMC.glActiveTexture(GL32.GL_TEXTURE1);
GLMC.glBindTexture(LodRenderer.getActiveDepthTextureId());
GL32.glUniform1i(this.depthTextureUniform, 1);
}
@@ -28,9 +28,11 @@ import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.render.renderer.ScreenQuad;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import org.lwjgl.opengl.GL32;
import java.awt.*;
@@ -41,11 +43,14 @@ public class FogShader extends AbstractShaderRenderer
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
public int frameBuffer;
private Mat4f inverseMvmProjMatrix;
private Mat4f inverseMvmProjMatrix;
//==========//
@@ -253,7 +258,15 @@ public class FogShader extends AbstractShaderRenderer
// this is necessary for MC 1.16 (IE Legacy OpenGL)
// otherwise the framebuffer isn't cleared correctly and the fog smears across the screen
GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT);
if (MC_RENDER.runningLegacyOpenGL())
{
// in another part of the DH code we set the fog color to opaque, here it needs to be transparent
float[] clearColorValues = new float[4];
GL32.glGetFloatv(GL32.GL_COLOR_CLEAR_VALUE, clearColorValues);
GL32.glClearColor(clearColorValues[0], clearColorValues[1], clearColorValues[2], 0.0f);
GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT);
}
ScreenQuad.INSTANCE.render();
@@ -134,9 +134,6 @@ public class SSAOApplyShader extends AbstractShaderRenderer
// it should be automatically restored after rendering is complete.
GLMC.disableDepthTest();
GLMC.glActiveTexture(GL32.GL_TEXTURE0);
GLMC.glBindTexture(0);
// apply the rendered SSAO to the LODs
GLMC.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, SSAOShader.INSTANCE.frameBuffer);
GLMC.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, LodRenderer.getActiveFramebufferId());
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.sql.DbConnectionClosedException;
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
import com.seibel.distanthorizons.core.sql.repo.phantoms.AutoClosableTrackingWrapper;
import com.seibel.distanthorizons.core.util.KeyedLockContainer;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
@@ -501,7 +502,12 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
}
else
{
LOGGER.warn("Attempting to close already closed database connection: [" + connectionString + "]");
// these warnings can be ignored in release builds, as long as the connection is closed it doesn't really matter
// TODO fix duplicate closes
if (ModInfo.IS_DEV_BUILD)
{
LOGGER.warn("Attempting to close already closed database connection: [" + connectionString + "]");
}
}
}
}
@@ -562,7 +568,12 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
}
else
{
LOGGER.warn("Attempting to close already closed database connection: [" + this.connectionString + "]");
// these warnings can be ignored in release builds, as long as the connection is closed it doesn't really matter
// TODO fix duplicate closes
if (ModInfo.IS_DEV_BUILD)
{
LOGGER.warn("Attempting to close already closed database connection: [" + this.connectionString + "]");
}
}
}
ACTIVE_CONNECTION_STRINGS_BY_REPO.remove(this);
@@ -78,9 +78,11 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor
long runTime = System.nanoTime() - this.runStartTime.get();
Thread.sleep(TimeUnit.NANOSECONDS.toMillis((long) (runTime / this.runTimeRatioConfig.get() - runTime)));
}
catch (InterruptedException e)
catch (InterruptedException ignore)
{
throw new RuntimeException(e);
// if this thread is interrupted that means the
// thread pool is being shut down,
// we don't need to log that.
}
}
@@ -89,7 +89,14 @@ public class ThreadPoolUtil
public static void setupThreadPools()
{
// thread pools
//==================//
// main thread pool //
//==================//
if (taskPicker != null)
{
taskPicker.shutdown();
}
taskPicker = new PriorityTaskPicker();
networkCompressionThreadPool = taskPicker.createExecutor();
@@ -98,8 +105,22 @@ public class ThreadPoolUtil
updatePropagatorThreadPool = taskPicker.createExecutor();
worldGenThreadPool = taskPicker.createExecutor();
// single thread pools
//=========================//
// standalone thread pools //
//=========================//
if (beaconCullingThreadPool != null)
{
beaconCullingThreadPool.shutdown();
}
beaconCullingThreadPool = ThreadUtil.makeSingleThreadPool(BEACON_CULLING_THREAD_NAME);
if (fullDataMigrationThreadPool != null)
{
fullDataMigrationThreadPool.shutdown();
}
fullDataMigrationThreadPool = ThreadUtil.makeSingleThreadPool(FULL_DATA_MIGRATION_THREAD_NAME);
}
@@ -39,8 +39,8 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
public final ClientOnlySaveStructure saveStructure;
public final ClientNetworkState networkState = new ClientNetworkState();
public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client World Ticker Thread");
public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick);
public final ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client World Ticker Thread");
public final EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick);
@@ -128,6 +128,8 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
{
this.networkState.close();
this.dhTickerThread.shutdownNow();
for (DhClientLevel dhClientLevel : this.levels.values())
{
@@ -37,6 +37,7 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
}
//================//
// level handling //
//================//
@@ -23,9 +23,11 @@ import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.NotNull;
import java.io.Closeable;
import java.util.concurrent.CompletableFuture;
public interface IDhWorld
// TODO why is this exist alongside AbstractDhWorld?
public interface IDhWorld extends Closeable
{
IDhLevel getOrLoadLevel(@NotNull ILevelWrapper levelWrapper);
@@ -49,6 +49,11 @@ public interface IBlockStateWrapper extends IDhApiBlockStateWrapper
boolean isBeaconBlock();
/** IE a glass block that can affect the beacon beam color */
boolean isBeaconTintBlock();
/**
* Returns true for any blocks that allow beacon beams to go through.
* IE: glass, stairs, bedrock, chests, end portal frames, carpet, cake
*/
boolean allowsBeaconBeamPassage();
/**
* The blocks used by a beacon's base
* IE Iron, diamond, gold, etc.
@@ -381,8 +381,9 @@ public interface IChunkWrapper extends IBindable
for (int y = beaconRelPos.getY() +1; y <= maxY; y++)
{
IBlockStateWrapper block = centerChunk.getBlockState(beaconRelPos.getX(), y, beaconRelPos.getZ());
if (!block.isAir() && block.getOpacity() == LodUtil.BLOCK_FULLY_OPAQUE)
if (!block.allowsBeaconBeamPassage())
{
// beam is blocked by this block
return null;
}
@@ -60,6 +60,9 @@ public interface IMinecraftRenderWrapper extends IBindable
int getScreenWidth();
int getScreenHeight();
boolean mcRendersToFrameBuffer();
boolean runningLegacyOpenGL();
/** @return -1 if no valid framebuffer is available yet */
int getTargetFrameBuffer(); // Note: Iris is now hooking onto this for DH + Iris compat, try not to change (unless we wanna deal with some annoyances)
// Iris commit: https://github.com/IrisShaders/Iris/commit/a76a240527e93780bbcba57c09bef377419d47a7#diff-7b9ded0c79bbcdb130010373387756a28ee8d3640d522c0a5b7acd0abbfc20aeR16
@@ -746,6 +746,10 @@
"Maximum Data Transfer Speed, KB/s",
"distanthorizons.config.server.maxDataTransferSpeed.@tooltip":
"Maximum speed for uploading LODs to the clients, in KB/s.\nValue of 0 disables the limit.",
"distanthorizons.config.server.enableAdaptiveTransferSpeed":
"Enable Adaptive Transfer Speed",
"distanthorizons.config.server.enableAdaptiveTransferSpeed.@tooltip":
"Enables adaptive transfer speed based on client performance.\nIf true, DH will automatically adjust transfer rate to minimize connection lag.\nIf false, transfer speed will remain fixed.",
"distanthorizons.config.server.experimental":
+13 -4
View File
@@ -8,17 +8,26 @@ uniform sampler2D gDhColorTexture;
uniform sampler2D gDhDepthTexture;
/**
* LOD application shader
*
* This merges the rendered LODs into Minecraft's texture/FBO
*/
void main()
{
fragColor = vec4(0.0);
float fragmentDepth = texture(gDhDepthTexture, TexCoord).r;
// a fragment depth of "1" means the fragment wasn't drawn to,
// only update fragments that were drawn to
if (fragmentDepth != 1)
float fragmentDepth = texture(gDhDepthTexture, TexCoord).r;
if (fragmentDepth != 1)
{
fragColor = texture(gDhColorTexture, TexCoord);
}
else
{
// use the original MC texture if no LODs were drawn to this fragment
discard;
}
}
@@ -8,16 +8,19 @@ uniform sampler2D uColorTexture;
uniform sampler2D uDepthTexture;
/**
* Fog application shader
*
* This merges the rendered fog onto DH's rendered LODs
*/
void main()
{
fragColor = vec4(1.0);
float fragmentDepth = textureLod(uDepthTexture, TexCoord, 0).r;
fragColor = vec4(0.0);
// a fragment depth of "1" means the fragment wasn't drawn to,
// only update fragments that were drawn to
if (fragmentDepth != 1)
float fragmentDepth = textureLod(uDepthTexture, TexCoord, 0).r;
if (fragmentDepth != 1)
{
fragColor = texture(uColorTexture, TexCoord);
}