Compare commits

...

34 Commits

Author SHA1 Message Date
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
James Seibel f9bd7e2daf remove dev from version number 2025-03-25 07:16:43 -05:00
James Seibel 8ec4e235eb Add config to only log GL errors once 2025-03-19 22:02:53 -05:00
James Seibel b8a59d0ef6 Attempt to fix Linux complaining about glIsFramebuffer() 2025-03-19 18:34:02 -05:00
James Seibel e500143781 Potentially fix GL errors when accessing the default FBO on Linux 2025-03-19 17:00:30 -05:00
James Seibel 406468b54c Fix restoring textures to the default FBO 2025-03-18 20:18:13 -05:00
James Seibel 6857300ae2 Add stack tracing to GL error logging 2025-03-18 18:10:00 -05:00
James Seibel 6775ee23c3 fix passing in the wrong flags to glBufferStorage()
Might Resolve #964 and #950
2025-03-18 07:43:20 -05:00
James Seibel 44645943e2 Fix terrain API repo failing if no cache was provided 2025-03-18 07:42:31 -05:00
James Seibel f385c4a56b Fix changing graphics settings on world load via API 2025-03-16 14:29:56 -05:00
James Seibel 0cf5e6d594 Fix GL error logging 2025-03-14 10:17:52 -05:00
James Seibel 7b5b8da0d2 decrease gen message timeout 5 -> 2 seconds
done to make it consistently appear
2025-03-13 21:17:01 -05:00
James Seibel 851f2ccd06 Add additional error checking/handling to Shader compiling 2025-03-13 21:12:29 -05:00
James Seibel 6c40389c07 up version number 2.3.0-b -> 2.3.1-b-dev 2025-03-08 08:11:01 -06:00
James Seibel fada9e4cf6 Fix repo leak unit test failing in release 2025-03-08 08:10:43 -06:00
James Seibel 06198fdbb8 Revert "temporarily disable sqlite tests for release"
This reverts commit ebc1114a51.
2025-03-06 07:43:14 -06:00
James Seibel 3158eed5a3 Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons-core 2025-03-06 07:42:32 -06:00
s809 d2ff4a5806 Add some debugging info for DTOs 2025-03-02 20:08:45 +05:00
48 changed files with 1116 additions and 675 deletions
@@ -9,12 +9,15 @@ package com.seibel.distanthorizons.api.interfaces.data;
* @version 2024-7-14 * @version 2024-7-14
* @since API 3.0.0 * @since API 3.0.0
*/ */
public interface IDhApiTerrainDataCache public interface IDhApiTerrainDataCache // TODO should this be AutoClosable?
{ {
/** /**
* Removes any data that's currently stored in this cache. * Removes any data that's currently stored in this cache.
* This cane be done to free up memory or invalidate * This cane be done to free up memory or invalidate
* the cache so fresh data can be pulled in. * the cache so fresh data can be pulled in.
* <br><br>
* This should be called before de-referencing this object
* so DH can handle any necessary cleanup for internal objects.
*/ */
void clear(); void clear();
@@ -38,7 +38,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 = "2.3.0-b"; public static final String VERSION = "2.3.2-b";
/** 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");
@@ -197,10 +197,10 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper; ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper;
if (!(apiDataCache instanceof DhApiTerrainDataCache)) // the data cache can be null, but must be our own implementation
if (apiDataCache != null
&& !(apiDataCache instanceof DhApiTerrainDataCache))
{ {
// custom level wrappers aren't supported,
// the API user must get a level wrapper from our code somewhere
return DhApiResult.createFail("Unsupported [" + IDhApiTerrainDataCache.class.getSimpleName() + "] implementation, only the core class [" + DhApiTerrainDataCache.class.getSimpleName() + "] is a valid parameter."); return DhApiResult.createFail("Unsupported [" + IDhApiTerrainDataCache.class.getSimpleName() + "] implementation, only the core class [" + DhApiTerrainDataCache.class.getSimpleName() + "] is a valid parameter.");
} }
DhApiTerrainDataCache dataCache = (DhApiTerrainDataCache) apiDataCache; DhApiTerrainDataCache dataCache = (DhApiTerrainDataCache) apiDataCache;
@@ -226,10 +226,9 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
// get the data source // // get the data source //
//=====================// //=====================//
FullDataSourceV2 dataSource = null;
try try
{ {
FullDataSourceV2 dataSource = null;
// try using the cached data if possible // try using the cached data if possible
if (dataCache != null) if (dataCache != null)
{ {
@@ -244,7 +243,12 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
{ {
return DhApiResult.createFail("Unable to find/generate any data at the " + DhSectionPos.class.getSimpleName() + " [" + DhSectionPos.toString(sectionPos) + "]."); return DhApiResult.createFail("Unable to find/generate any data at the " + DhSectionPos.class.getSimpleName() + " [" + DhSectionPos.toString(sectionPos) + "].");
} }
dataCache.add(sectionPos, dataSource);
// save to the cache if present
if (dataCache != null)
{
dataCache.add(sectionPos, dataSource);
}
} }
@@ -316,6 +320,14 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
LOGGER.error("Unexpected exception in getTerrainDataColumnArray. Error: [" + e.getMessage() + "]", e); LOGGER.error("Unexpected exception in getTerrainDataColumnArray. Error: [" + e.getMessage() + "]", e);
return DhApiResult.createFail("Unexpected exception: [" + e.getMessage() + "]."); return DhApiResult.createFail("Unexpected exception: [" + e.getMessage() + "].");
} }
finally
{
if (dataCache == null
&& dataSource != null)
{
dataSource.close();
}
}
} }
@@ -99,6 +99,11 @@ public class SharedApi
public static void setDhWorld(AbstractDhWorld newWorld) public static void setDhWorld(AbstractDhWorld newWorld)
{ {
AbstractDhWorld oldWorld = currentWorld;
if (oldWorld != null)
{
oldWorld.close();
}
currentWorld = newWorld; currentWorld = newWorld;
// starting and stopping the DataRenderTransformer is necessary to prevent attempting to // starting and stopping the DataRenderTransformer is necessary to prevent attempting to
@@ -1015,9 +1015,23 @@ public class Config
public static class OpenGl public static class OpenGl
{ {
public static ConfigEntry<Boolean> overrideVanillaGLLogger = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> overrideVanillaGLLogger = new ConfigEntry.Builder<Boolean>()
.set(ModInfo.IS_DEV_BUILD) .set(true)
.comment("" .comment(""
+ "Requires a reboot to change. \n" + "Defines how OpenGL errors are handled. \n "
+ "Requires rebooting Minecraft to change. \n"
+ "Will catch OpenGL errors thrown by other mods. \n"
+ "")
.build();
public static ConfigEntry<Boolean> onlyLogGlErrorsOnce = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If true each Open GL error will only be logged once. \n"
+ "Enabling this may cause some error logs to be missed. \n"
+ "Does nothing if overrideVanillaGLLogger is set to false. \n"
+ " \n"
+ "Generally this can be kept as 'true' to prevent log spam. \n"
+ "However, Please set this to 'false' if a developer needs your log to debug a GL issue. \n"
+ "") + "")
.build(); .build();
@@ -1281,7 +1295,7 @@ public class Config
public static ConfigEntry<Integer> generationProgressDisplayIntervalInSeconds = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> generationProgressDisplayIntervalInSeconds = new ConfigEntry.Builder<Integer>()
.setChatCommandName("generation.logInterval") .setChatCommandName("generation.logInterval")
.setMinDefaultMax(1, 5, 60 * 60 * 4) // max = 4 hours .setMinDefaultMax(1, 2, 60 * 60 * 4) // max = 4 hours
.comment("" .comment(""
+ "How often should the distant generator progress be displayed? \n" + "How often should the distant generator progress be displayed? \n"
+ "") + "")
@@ -328,7 +328,8 @@ public class DhLightingEngine
continue; 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, // the light pos is outside the chunk's min/max height,
// this can happen if given a chunk that hasn't finished generating // this can happen if given a chunk that hasn't finished generating
@@ -258,7 +258,11 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
public void clearRenderCache() public void clearRenderCache()
{ {
this.clientLevel.getClientLevelWrapper().clearBlockColorCache(); IClientLevelWrapper clientLevelWrapper = this.clientLevel.getClientLevelWrapper();
if (clientLevelWrapper != null)
{
clientLevelWrapper.clearBlockColorCache();
}
ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); ClientRenderState ClientRenderState = this.ClientRenderStateRef.get();
if (ClientRenderState != null && ClientRenderState.quadtree != null) if (ClientRenderState != null && ClientRenderState.quadtree != null)
@@ -29,6 +29,7 @@ import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV
import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataSourceProvider; import com.seibel.distanthorizons.core.file.fullDatafile.RemoteFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue; import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState; import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue; import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
@@ -47,6 +48,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrap
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -63,6 +65,9 @@ import java.util.concurrent.TimeUnit;
public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
protected static final ConfigBasedLogger NETWORK_LOGGER = new ConfigBasedLogger(LogManager.getLogger(),
() -> Config.Common.Logging.logNetworkEvent.get());
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public final ClientLevelModule clientside; public final ClientLevelModule clientside;
@@ -159,7 +164,9 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSourceAndReleaseBuffer(message.payload)) try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSourceAndReleaseBuffer(message.payload))
{ {
if (!message.isSameLevelAs(this.levelWrapper)) boolean isSameLevel = message.isSameLevelAs(this.levelWrapper);
NETWORK_LOGGER.debug("Buffer {} isSameLevel: {}", message.payload.dtoBufferId, isSameLevel);
if (!isSameLevel)
{ {
return; return;
} }
@@ -35,6 +35,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.Nullable;
import java.awt.*; import java.awt.*;
import java.util.List; import java.util.List;
@@ -107,6 +108,7 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
} }
} }
@Nullable
@Override @Override
public IClientLevelWrapper getClientLevelWrapper() { return MC_CLIENT.getWrappedClientLevel(); } public IClientLevelWrapper getClientLevelWrapper() { return MC_CLIENT.getWrappedClientLevel(); }
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrappe
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.jetbrains.annotations.Nullable;
public interface IDhClientLevel extends IDhLevel public interface IDhClientLevel extends IDhLevel
{ {
@@ -35,6 +36,7 @@ public interface IDhClientLevel extends IDhLevel
int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block); int computeBaseColor(DhBlockPos pos, IBiomeWrapper biome, IBlockStateWrapper block);
@Nullable
IClientLevelWrapper getClientLevelWrapper(); IClientLevelWrapper getClientLevelWrapper();
/** /**
@@ -290,7 +290,7 @@ public class WorldGenModule implements Closeable
remainingChunkCount += this.worldGenerationQueue.getQueuedChunkCount(); remainingChunkCount += this.worldGenerationQueue.getQueuedChunkCount();
String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount); 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 // show a message about how to disable progress logging if requested
int msToShowDisableInstructions = Config.Common.WorldGenerator.generationProgressDisableMessageDisplayTimeInSeconds.get() * 1_000; int msToShowDisableInstructions = Config.Common.WorldGenerator.generationProgressDisableMessageDisplayTimeInSeconds.get() * 1_000;
@@ -69,19 +69,21 @@ public class FullDataPayloadReceiver implements AutoCloseable
}); });
} }
public FullDataSourceV2DTO decodeDataSourceAndReleaseBuffer(FullDataPayload msg) public FullDataSourceV2DTO decodeDataSourceAndReleaseBuffer(FullDataPayload payload)
{ {
CompositeByteBuf compositeByteBuffer = this.buffersById.get(msg.dtoBufferId); CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
LodUtil.assertTrue(compositeByteBuffer != null); LodUtil.assertTrue(compositeByteBuffer != null);
try try
{ {
return INetworkObject.decodeToInstance(FullDataSourceV2DTO.CreateEmptyDataSourceForDecoding(), compositeByteBuffer); FullDataSourceV2DTO dataSourceDto = INetworkObject.decodeToInstance(FullDataSourceV2DTO.CreateEmptyDataSourceForDecoding(), compositeByteBuffer);
LOGGER.debug("Buffer {} DTO: {}", payload.dtoBufferId, dataSourceDto);
return dataSourceDto;
} }
finally finally
{ {
// Releasing the buffer is handled by cache // Releasing the buffer is handled by cache
this.buffersById.remove(msg.dtoBufferId); this.buffersById.remove(payload.dtoBufferId);
} }
} }
@@ -510,10 +510,13 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{ {
// TODO memoization is needed for multiplayer, otherwise // TODO memoization is needed for multiplayer, otherwise
// new retrieval requests won't be submitted. // 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.missingGenerationPosFunc = Suppliers.memoizeWithExpiration(
() -> this.fullDataSourceProvider.getPositionsToRetrieve(this.pos), () -> this.fullDataSourceProvider.getPositionsToRetrieve(this.pos),
15, TimeUnit.SECONDS); 10, TimeUnit.MINUTES);
} }
LongArrayList missingGenerationPos = this.getMissingGenerationPos(); LongArrayList missingGenerationPos = this.getMissingGenerationPos();
@@ -359,6 +359,7 @@ public class RenderBufferHandler implements AutoCloseable
// debug wireframe setup // // 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(); boolean renderWireframe = Config.Client.Advanced.Debugging.renderWireframe.get();
if (renderWireframe) if (renderWireframe)
{ {
@@ -25,10 +25,10 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.objects.GLMessage; import com.seibel.distanthorizons.core.util.objects.GLMessages.*;
import com.seibel.distanthorizons.core.util.objects.GLMessageOutputStream;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
@@ -38,31 +38,28 @@ import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.opengl.GLUtil; import org.lwjgl.opengl.GLUtil;
import java.io.PrintStream; import java.io.PrintStream;
import java.lang.invoke.MethodHandles; import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
/** /**
* A singleton that holds references to different openGL contexts * A singleton that holds references to different openGL contexts
* and GPU capabilities. * and GPU capabilities.
*
* <p>
* Helpful OpenGL resources:
* <p>
* https://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf <br>
* https://learnopengl.com/Advanced-OpenGL/Advanced-Data <br>
* https://www.slideshare.net/CassEveritt/approaching-zero-driver-overhead <br><br>
*
* https://gamedev.stackexchange.com/questions/91995/edit-vbo-data-or-create-a-new-one <br>
* https://stackoverflow.com/questions/63509735/massive-performance-loss-with-glmapbuffer <br><br>
*/ */
public class GLProxy public class GLProxy
{ {
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public static final ConfigBasedLogger GL_LOGGER = new ConfigBasedLogger(LogManager.getLogger(GLProxy.class), public static final ConfigBasedLogger GL_LOGGER = new ConfigBasedLogger(LogManager.getLogger(GLProxy.class),
() -> Config.Common.Logging.logRendererGLEvent.get()); () -> Config.Common.Logging.logRendererGLEvent.get());
public static final Set<String> LOGGED_GL_MESSAGES = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
private static GLProxy instance = null; private static GLProxy instance = null;
@@ -79,7 +76,29 @@ public class GLProxy
private final EDhApiGpuUploadMethod preferredUploadMethod; private final EDhApiGpuUploadMethod preferredUploadMethod;
public final GLMessage.Builder vanillaDebugMessageBuilder = GLMessage.Builder.DEFAULT_MESSAGE_BUILDER; public final GLMessageBuilder vanillaDebugMessageBuilder =
new GLMessageBuilder(
(type) ->
{
if (type == EGLMessageType.POP_GROUP)
return false;
else if (type == EGLMessageType.PUSH_GROUP)
return false;
else if (type == EGLMessageType.MARKER)
return false;
else
return true;
},
(severity) ->
{
// notifications can generally be ignored (if they are logged at all)
if (severity == EGLMessageSeverity.NOTIFICATION)
return false;
else
return true;
},
null
);
@@ -258,6 +277,7 @@ public class GLProxy
// logging // // logging //
//=========// //=========//
/** this method is called on the render thread at the point of the GL Error */
private static void logMessage(GLMessage msg) private static void logMessage(GLMessage msg)
{ {
EDhApiGLErrorHandlingMode errorHandlingMode = Config.Client.Advanced.Debugging.OpenGl.glErrorHandlingMode.get(); EDhApiGLErrorHandlingMode errorHandlingMode = Config.Client.Advanced.Debugging.OpenGl.glErrorHandlingMode.get();
@@ -268,44 +288,56 @@ public class GLProxy
if (msg.type == GLMessage.EType.ERROR || msg.type == GLMessage.EType.UNDEFINED_BEHAVIOR) boolean onlyLogOnce = Config.Client.Advanced.Debugging.OpenGl.onlyLogGlErrorsOnce.get();
String errorMessage = "GL ERROR [" + msg.id + "] from [" + msg.source + "]: [" + msg.message + "]"+(onlyLogOnce ? " this message will only be logged once" : "")+".";
if (onlyLogOnce
&& !LOGGED_GL_MESSAGES.add(errorMessage))
{
// this message has already been logged
return;
}
// create an exception so we get a stacktrace of where the message was triggered from
RuntimeException exception = new RuntimeException(errorMessage);
if (msg.type == EGLMessageType.ERROR || msg.type == EGLMessageType.UNDEFINED_BEHAVIOR)
{ {
// critical error // critical error
GL_LOGGER.error("GL ERROR " + msg.id + " from " + msg.source + ": " + msg.message); GL_LOGGER.error(exception.getMessage(), exception);
if (errorHandlingMode == EDhApiGLErrorHandlingMode.LOG_THROW) if (errorHandlingMode == EDhApiGLErrorHandlingMode.LOG_THROW)
{ {
throw new RuntimeException("GL ERROR: " + msg); // will probably crash the game,
// good for quickly checking if there's a problem while preventing log spam
throw exception;
} }
} }
else else
{ {
// non-critical log // non-critical log
GLMessage.ESeverity severity = msg.severity; EGLMessageSeverity severity = msg.severity;
RuntimeException ex = new RuntimeException("GL MESSAGE: " + msg);
if (severity == null) if (severity == null)
{ {
// just in case the message was malformed // just in case the message was malformed
severity = GLMessage.ESeverity.LOW; severity = EGLMessageSeverity.LOW;
} }
switch (severity) switch (severity)
{ {
case HIGH: case HIGH:
GL_LOGGER.error("{}", ex); GL_LOGGER.error(exception.getMessage(), exception);
break; break;
case MEDIUM: case MEDIUM:
GL_LOGGER.warn("{}", ex); GL_LOGGER.warn(exception.getMessage(), exception);
break; break;
case LOW: case LOW:
GL_LOGGER.info("{}", ex); GL_LOGGER.info(exception.getMessage(), exception);
break; break;
case NOTIFICATION: case NOTIFICATION:
GL_LOGGER.debug("{}", ex); GL_LOGGER.debug(exception.getMessage(), exception);
break; break;
} }
} }
@@ -35,7 +35,7 @@ public class GLState
public int vao; public int vao;
public int vbo; public int vbo;
public int ebo; public int ebo;
public int[] fbo; public int fbo;
public int texture2D; public int texture2D;
/** IE: GL_TEXTURE0, GL_TEXTURE1, etc. */ /** IE: GL_TEXTURE0, GL_TEXTURE1, etc. */
public int activeTextureNumber; public int activeTextureNumber;
@@ -57,10 +57,10 @@ public class GLState
public boolean depth; public boolean depth;
public boolean writeToDepthBuffer; public boolean writeToDepthBuffer;
public int depthFunc; public int depthFunc;
//public boolean stencil; public boolean stencil;
//public int stencilFunc; public int stencilFunc;
//public int stencilRef; public int stencilRef;
//public int stencilMask; public int stencilMask;
public int[] view; public int[] view;
public boolean cull; public boolean cull;
public int cullMode; public int cullMode;
@@ -68,12 +68,7 @@ public class GLState
public GLState() public GLState() { this.saveState(); }
{
this.fbo = new int[FBO_MAX];
this.saveState();
}
public void saveState() public void saveState()
{ {
@@ -82,7 +77,7 @@ public class GLState
this.vbo = GL32.glGetInteger(GL32.GL_ARRAY_BUFFER_BINDING); this.vbo = GL32.glGetInteger(GL32.GL_ARRAY_BUFFER_BINDING);
this.ebo = GL32.glGetInteger(GL32.GL_ELEMENT_ARRAY_BUFFER_BINDING); this.ebo = GL32.glGetInteger(GL32.GL_ELEMENT_ARRAY_BUFFER_BINDING);
GL32.glGetIntegerv(GL32.GL_FRAMEBUFFER_BINDING, this.fbo); this.fbo = GL32.glGetInteger(GL32.GL_FRAMEBUFFER_BINDING);
this.texture2D = GL32.glGetInteger(GL32.GL_TEXTURE_BINDING_2D); this.texture2D = GL32.glGetInteger(GL32.GL_TEXTURE_BINDING_2D);
this.activeTextureNumber = GL32.glGetInteger(GL32.GL_ACTIVE_TEXTURE); this.activeTextureNumber = GL32.glGetInteger(GL32.GL_ACTIVE_TEXTURE);
@@ -101,9 +96,19 @@ public class GLState
GLMC.glActiveTexture(this.activeTextureNumber); GLMC.glActiveTexture(this.activeTextureNumber);
this.frameBufferTexture0 = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME); if (this.fbo != 0)
this.frameBufferTexture1 = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT1, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME); {
this.frameBufferDepthTexture = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_DEPTH_ATTACHMENT, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME); this.frameBufferTexture0 = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
this.frameBufferTexture1 = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT1, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
this.frameBufferDepthTexture = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_DEPTH_ATTACHMENT, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
}
else
{
// attempting to get values from the default framebuffer can throw errors on Linux
this.frameBufferTexture0 = 0;
this.frameBufferTexture1 = 0;
this.frameBufferDepthTexture = 0;
}
this.blend = GL32.glIsEnabled(GL32.GL_BLEND); this.blend = GL32.glIsEnabled(GL32.GL_BLEND);
this.scissor = GL32.glIsEnabled(GL32.GL_SCISSOR_TEST); this.scissor = GL32.glIsEnabled(GL32.GL_SCISSOR_TEST);
@@ -116,10 +121,10 @@ public class GLState
this.depth = GL32.glIsEnabled(GL32.GL_DEPTH_TEST); this.depth = GL32.glIsEnabled(GL32.GL_DEPTH_TEST);
this.writeToDepthBuffer = GL32.glGetInteger(GL32.GL_DEPTH_WRITEMASK) == GL32.GL_TRUE; this.writeToDepthBuffer = GL32.glGetInteger(GL32.GL_DEPTH_WRITEMASK) == GL32.GL_TRUE;
this.depthFunc = GL32.glGetInteger(GL32.GL_DEPTH_FUNC); this.depthFunc = GL32.glGetInteger(GL32.GL_DEPTH_FUNC);
//this.stencil = GL32.glIsEnabled(GL32.GL_STENCIL_TEST); this.stencil = GL32.glIsEnabled(GL32.GL_STENCIL_TEST);
//this.stencilFunc = GL32.glGetInteger(GL32.GL_STENCIL_FUNC); this.stencilFunc = GL32.glGetInteger(GL32.GL_STENCIL_FUNC);
//this.stencilRef = GL32.glGetInteger(GL32.GL_STENCIL_REF); this.stencilRef = GL32.glGetInteger(GL32.GL_STENCIL_REF);
//this.stencilMask = GL32.glGetInteger(GL32.GL_STENCIL_VALUE_MASK); this.stencilMask = GL32.glGetInteger(GL32.GL_STENCIL_VALUE_MASK);
this.view = new int[4]; this.view = new int[4];
GL32.glGetIntegerv(GL32.GL_VIEWPORT, this.view); GL32.glGetIntegerv(GL32.GL_VIEWPORT, this.view);
this.cull = GL32.glIsEnabled(GL32.GL_CULL_FACE); this.cull = GL32.glIsEnabled(GL32.GL_CULL_FACE);
@@ -131,38 +136,33 @@ public class GLState
public String toString() public String toString()
{ {
return "GLState{" + return "GLState{" +
"program=" + this.program + ", vao=" + this.vao + ", vbo=" + this.vbo + ", ebo=" + this.ebo + ", fbo=" + this.fbo[0] + "program=" + this.program + ", vao=" + this.vao + ", vbo=" + this.vbo + ", ebo=" + this.ebo + ", fbo=" + this.fbo +
", text=" + GLEnums.getString(this.texture2D) + "@" + this.activeTextureNumber + ", text0=" + GLEnums.getString(this.texture0) + ", text=" + GLEnums.getString(this.texture2D) + "@" + this.activeTextureNumber + ", text0=" + GLEnums.getString(this.texture0) +
", FB text0=" + this.frameBufferTexture0 + ", FB text0=" + this.frameBufferTexture0 +
", FB text1=" + this.frameBufferTexture1 + ", FB text1=" + this.frameBufferTexture1 +
", FB depth=" + this.frameBufferDepthTexture + ", FB depth=" + this.frameBufferDepthTexture +
", blend=" + this.blend + ", scissor=" + this.scissor + ", blendMode=" + GLEnums.getString(this.blendSrcColor) + "," + GLEnums.getString(this.blendDstColor) + ", blend=" + this.blend + ", scissor=" + this.scissor + ", blendMode=" + GLEnums.getString(this.blendSrcColor) + "," + GLEnums.getString(this.blendDstColor) +
", depth=" + this.depth + ", depth=" + this.depth +
//", depthFunc=" + GLEnums.getString(this.depthFunc) + ", stencil=" + this.stencil + ", stencilFunc=" + ", depthFunc=" + GLEnums.getString(this.depthFunc) + ", stencil=" + this.stencil +
//GLEnums.getString(this.stencilFunc) + ", stencilRef=" + this.stencilRef + ", stencilMask=" + this.stencilMask + ", stencilFunc=" + GLEnums.getString(this.stencilFunc) + ", stencilRef=" + this.stencilRef + ", stencilMask=" + this.stencilMask +
", view={x:" + this.view[0] + ", y:" + this.view[1] + ", view={x:" + this.view[0] + ", y:" + this.view[1] +
", w:" + this.view[2] + ", h:" + this.view[3] + "}" + ", cull=" + this.cull + ", cullMode=" ", w:" + this.view[2] + ", h:" + this.view[3] + "}" + ", cull=" + this.cull +
+ GLEnums.getString(this.cullMode) + ", polyMode=" + GLEnums.getString(this.polyMode) + ", cullMode=" + GLEnums.getString(this.cullMode) + ", polyMode=" + GLEnums.getString(this.polyMode) +
'}'; '}';
} }
public void RestoreFrameBuffer()
{
// explicitly unbinding the frame buffer is necessary to prevent GL_CLEAR calls from hitting the wrong buffer
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, 0);
for (int i = 0; i < FBO_MAX; i++)
{
int buffer = this.fbo[i];
if (i > 0 && buffer == 0) break;
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, GL32.glIsFramebuffer(buffer) ? buffer : 0);
}
}
public void restore() public void restore()
{ {
this.RestoreFrameBuffer(); // explicitly unbinding the frame buffer is necessary to prevent GL_CLEAR calls from hitting the wrong buffer
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, 0);
boolean frameBufferSet = false;
if (this.fbo != 0 && GL32.glIsFramebuffer(this.fbo))
{
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fbo);
frameBufferSet = true;
}
if (this.blend) if (this.blend)
{ {
@@ -197,9 +197,13 @@ public class GLState
GLMC.glActiveTexture(this.activeTextureNumber); GLMC.glActiveTexture(this.activeTextureNumber);
GLMC.glBindTexture(GL32.glIsTexture(this.texture2D) ? this.texture2D : 0); GLMC.glBindTexture(GL32.glIsTexture(this.texture2D) ? this.texture2D : 0);
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.frameBufferTexture0, 0); // attempting to set textures on the default frame buffer (ID 0) will throw errors
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT1, GL32.GL_TEXTURE_2D, this.frameBufferTexture1, 0); if (frameBufferSet)
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_DEPTH_ATTACHMENT, GL32.GL_TEXTURE_2D, this.frameBufferDepthTexture, 0); {
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.frameBufferTexture0, 0);
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT1, GL32.GL_TEXTURE_2D, this.frameBufferTexture1, 0);
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_DEPTH_ATTACHMENT, GL32.GL_TEXTURE_2D, this.frameBufferDepthTexture, 0);
}
GL32.glBindVertexArray(GL32.glIsVertexArray(this.vao) ? this.vao : 0); GL32.glBindVertexArray(GL32.glIsVertexArray(this.vao) ? this.vao : 0);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, GL32.glIsBuffer(this.vbo) ? this.vbo : 0); GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, GL32.glIsBuffer(this.vbo) ? this.vbo : 0);
@@ -229,15 +233,15 @@ public class GLState
} }
GLMC.glDepthFunc(this.depthFunc); GLMC.glDepthFunc(this.depthFunc);
//if (this.stencil) if (this.stencil)
//{ {
// GL32.glEnable(GL32.GL_STENCIL_TEST); GL32.glEnable(GL32.GL_STENCIL_TEST);
//} }
//else else
//{ {
// GL32.glDisable(GL32.GL_STENCIL_TEST); GL32.glDisable(GL32.GL_STENCIL_TEST);
//} }
//GL32.glStencilFunc(this.stencilFunc, this.stencilRef, this.stencilMask); GL32.glStencilFunc(this.stencilFunc, this.stencilRef, this.stencilMask);
GL32.glViewport(this.view[0], this.view[1], this.view[2], this.view[3]); GL32.glViewport(this.view[0], this.view[1], this.view[2], this.view[3]);
if (this.cull) if (this.cull)
@@ -223,7 +223,7 @@ public class GLBuffer implements AutoCloseable
this.destroyAsync(); this.destroyAsync();
this.create(true); this.create(true);
this.bind(); this.bind();
GL44.glBufferStorage(this.getBufferBindingTarget(), bb, bufferStorageHint); GL44.glBufferStorage(this.getBufferBindingTarget(), bb, 0);
this.size = bbSize; this.size = bbSize;
} }
/** Requires the buffer to be bound */ /** Requires the buffer to be bound */
@@ -32,15 +32,18 @@ import org.lwjgl.opengl.GL32;
/** /**
* This object holds a OpenGL reference to a shader * This object holds a OpenGL reference to a shader
* and allows for reading in and compiling a shader file. * and allows for reading in and compiling a shader file.
*
* @author James Seibel
* @version 11-8-2021
*/ */
public class Shader public class Shader
{ {
/** OpenGL shader ID */ /** OpenGL shader ID */
public final int id; public final int id;
//==============//
// constructors //
//==============//
/** /**
* Creates a shader with specified type. * Creates a shader with specified type.
* *
@@ -51,51 +54,67 @@ public class Shader
*/ */
public Shader(int type, String path, boolean absoluteFilePath) public Shader(int type, String path, boolean absoluteFilePath)
{ {
GLProxy.GL_LOGGER.info("Loading shader at " + path); GLProxy.GL_LOGGER.info("Loading shader at [" + path + "]");
// Create an empty shader object // Create an empty shader object
id = GL32.glCreateShader(type); this.id = GL32.glCreateShader(type);
StringBuilder source = loadFile(path, absoluteFilePath, new StringBuilder()); if (this.id == 0)
GL32.glShaderSource(id, source); {
throw new IllegalArgumentException("Failed to create shader with type ["+type+"].");
}
GL32.glCompileShader(id); StringBuilder source = loadFile(path, absoluteFilePath, new StringBuilder());
GL32.glShaderSource(this.id, source);
GL32.glCompileShader(this.id);
// check if the shader compiled // check if the shader compiled
int status = GL32.glGetShaderi(id, GL32.GL_COMPILE_STATUS); int status = GL32.glGetShaderi(this.id, GL32.GL_COMPILE_STATUS);
if (status != GL32.GL_TRUE) if (status != GL32.GL_TRUE)
{ {
String message = "Shader compiler error. Details: " + GL32.glGetShaderInfoLog(id); String message = "Shader compiler error. Details: ["+GL32.glGetShaderInfoLog(this.id)+"].";
free(); // important! this.free(); // important!
throw new RuntimeException(message); throw new RuntimeException(message);
} }
GLProxy.GL_LOGGER.info("Shader at " + path + " loaded sucessfully."); GLProxy.GL_LOGGER.info("Shader at " + path + " loaded successfully.");
} }
public Shader(int type, String sourceString) public Shader(int type, String sourceString)
{ {
GLProxy.GL_LOGGER.info("Loading shader with type: {}", type); GLProxy.GL_LOGGER.info("Loading shader with type: ["+type+"]");
GLProxy.GL_LOGGER.debug("Source:\n{}", sourceString); GLProxy.GL_LOGGER.debug("Source: \n["+sourceString+"]");
// Create an empty shader object if (sourceString == null || sourceString.isEmpty())
id = GL32.glCreateShader(type); {
GL32.glShaderSource(id, sourceString); throw new IllegalArgumentException("No shader source given.");
}
GL32.glCompileShader(id); // Create an empty shader object
this.id = GL32.glCreateShader(type);
if (this.id == 0)
{
throw new IllegalArgumentException("Failed to create shader with type ["+type+"] and Source: \n["+sourceString+"].");
}
GL32.glShaderSource(this.id, sourceString);
GL32.glCompileShader(this.id);
// check if the shader compiled // check if the shader compiled
int status = GL32.glGetShaderi(id, GL32.GL_COMPILE_STATUS); int status = GL32.glGetShaderi(this.id, GL32.GL_COMPILE_STATUS);
if (status != GL32.GL_TRUE) if (status != GL32.GL_TRUE)
{ {
String message = "Shader compiler error. Details: " + GL32.glGetShaderInfoLog(id); String message = "Shader compiler error. Details: [" + GL32.glGetShaderInfoLog(this.id) + "]\n";
message += "\nSource:\n" + sourceString; message += "Source: \n[" + sourceString + "]";
free(); // important! this.free(); // important!
throw new RuntimeException(message); throw new RuntimeException(message);
} }
GLProxy.GL_LOGGER.info("Shader loaded sucessfully."); GLProxy.GL_LOGGER.info("Shader loaded sucessfully.");
} }
// REMEMBER to always free the resource!
public void free()
{ //=========//
GL32.glDeleteShader(id); // helpers //
} //=========//
public void free() { GL32.glDeleteShader(this.id); }
public static StringBuilder loadFile(String path, boolean absoluteFilePath, StringBuilder stringBuilder) public static StringBuilder loadFile(String path, boolean absoluteFilePath, StringBuilder stringBuilder)
{ {
@@ -86,22 +86,31 @@ public class FadeRenderer
this.fadeFramebuffer = -1; this.fadeFramebuffer = -1;
} }
if (this.fadeTexture != -1)
{
GLMC.glDeleteTextures(this.fadeTexture);
this.fadeTexture = -1;
}
this.fadeFramebuffer = GL32.glGenFramebuffers(); this.fadeFramebuffer = GL32.glGenFramebuffers();
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fadeFramebuffer); 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"); profiler.popPush("Fade Apply");
FadeApplyShader.INSTANCE.fadeTexture = this.fadeTexture; // Applying the fade texture is only needed if MC is drawing to their own frame buffer,
FadeApplyShader.INSTANCE.render(partialTicks); // otherwise we can directly render to their texture
if (MC_RENDER.mcRendersToFrameBuffer())
{
FadeApplyShader.INSTANCE.fadeTexture = this.fadeTexture;
FadeApplyShader.INSTANCE.render(partialTicks);
}
profiler.pop(); profiler.pop();
} }
@@ -557,34 +557,24 @@ public class LodRenderer
activeFrameBuffer.bind(); 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 // by default draw everything as triangles
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL); GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
GLMC.enableFaceCulling(); 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 // Enable depth test and depth mask
GLMC.enableDepthTest(); GLMC.enableDepthTest();
GLMC.glDepthFunc(GL32.GL_LESS); GLMC.glDepthFunc(GL32.GL_LESS);
GLMC.enableDepthMask(); 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--------*/ /*---------Bind required objects--------*/
// Setup LodRenderProgram and the LightmapTexture if it has not yet been done // Setup LodRenderProgram and the LightmapTexture if it has not yet been done
// also binds LightmapTexture, VAO, and ShaderProgram // also binds LightmapTexture, VAO, and ShaderProgram
@@ -605,6 +595,33 @@ public class LodRenderer
} }
this.lodRenderProgram.fillUniformData(renderEventParam); 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 */ /** 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) if(this.framebuffer.getStatus() != GL32.GL_FRAMEBUFFER_COMPLETE)
{ {
// This generally means something wasn't bound, IE missing either the color or depth texture // 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 // // API functions //
//===============// //===============//
@@ -748,7 +746,10 @@ public class LodRenderer
public static int getActiveColorTextureId() { return activeColorTextureId; } public static int getActiveColorTextureId() { return activeColorTextureId; }
private void setActiveDepthTextureId(int depthTextureId) { activeDepthTextureId = depthTextureId; } 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; } public static int getActiveDepthTextureId() { return activeDepthTextureId; }
@@ -75,6 +75,18 @@ public class DhApplyShader extends AbstractShaderRenderer
@Override @Override
protected void onRender() 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(); int targetFrameBuffer = MC_RENDER.getTargetFrameBuffer();
if (targetFrameBuffer == -1) if (targetFrameBuffer == -1)
@@ -87,9 +99,15 @@ public class DhApplyShader extends AbstractShaderRenderer
GLMC.disableDepthTest(); GLMC.disableDepthTest();
GLMC.enableBlend(); // blending isn't needed, we're manually merging the MC and DH textures
GL32.glBlendEquation(GL32.GL_FUNC_ADD); // Note: this prevents the sun/moon and stars from rendering through transparent LODs,
GLMC.glBlendFunc(GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA); // 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.glActiveTexture(GL32.GL_TEXTURE0);
GLMC.glBindTexture(LodRenderer.getActiveColorTextureId()); GLMC.glBindTexture(LodRenderer.getActiveColorTextureId());
@@ -110,5 +128,66 @@ public class DhApplyShader extends AbstractShaderRenderer
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, targetFrameBuffer); 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; package com.seibel.distanthorizons.core.render.renderer.shaders;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; 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.glObject.shader.ShaderProgram;
import com.seibel.distanthorizons.core.render.renderer.FadeRenderer; import com.seibel.distanthorizons.core.render.renderer.FadeRenderer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer; import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
@@ -104,6 +105,12 @@ public class FadeApplyShader extends AbstractShaderRenderer
@Override @Override
protected void onRender() 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(); GLMC.disableBlend();
// Depth testing must be disabled otherwise this application shader won't apply anything. // 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(); ScreenQuad.INSTANCE.render();
GLMC.enableDepthTest(); GLMC.enableDepthTest();
} }
} }
@@ -165,6 +165,7 @@ public class FadeShader extends AbstractShaderRenderer
GL32.glUniform1i(this.uMcDepthTexture, 0); GL32.glUniform1i(this.uMcDepthTexture, 0);
GLMC.glActiveTexture(GL32.GL_TEXTURE1); 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()); GLMC.glBindTexture(LodRenderer.getActiveDepthTextureId());
GL32.glUniform1i(this.uDhDepthTexture, 1); GL32.glUniform1i(this.uDhDepthTexture, 1);
@@ -84,6 +84,7 @@ public class FogApplyShader extends AbstractShaderRenderer
GLMC.glActiveTexture(GL32.GL_TEXTURE1); GLMC.glActiveTexture(GL32.GL_TEXTURE1);
GLMC.glBindTexture(LodRenderer.getActiveDepthTextureId()); GLMC.glBindTexture(LodRenderer.getActiveDepthTextureId());
GL32.glUniform1i(this.depthTextureUniform, 1); 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.LodRenderer;
import com.seibel.distanthorizons.core.render.renderer.ScreenQuad; import com.seibel.distanthorizons.core.render.renderer.ScreenQuad;
import com.seibel.distanthorizons.core.util.LodUtil; 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.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL32;
import java.awt.*; 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 IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.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; 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) // this is necessary for MC 1.16 (IE Legacy OpenGL)
// otherwise the framebuffer isn't cleared correctly and the fog smears across the screen // 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(); ScreenQuad.INSTANCE.render();
@@ -134,9 +134,6 @@ public class SSAOApplyShader extends AbstractShaderRenderer
// it should be automatically restored after rendering is complete. // it should be automatically restored after rendering is complete.
GLMC.disableDepthTest(); GLMC.disableDepthTest();
GLMC.glActiveTexture(GL32.GL_TEXTURE0);
GLMC.glBindTexture(0);
// apply the rendered SSAO to the LODs // apply the rendered SSAO to the LODs
GLMC.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, SSAOShader.INSTANCE.frameBuffer); GLMC.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, SSAOShader.INSTANCE.frameBuffer);
GLMC.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, LodRenderer.getActiveFramebufferId()); GLMC.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, LodRenderer.getActiveFramebufferId());
@@ -454,7 +454,7 @@ public class FullDataSourceV2DTO
{ {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
.add("levelMinY", this.levelMinY) .add("levelMinY", this.levelMinY)
.add("pos", this.pos) .add("pos", DhSectionPos.toString(this.pos))
.add("dataChecksum", this.dataChecksum) .add("dataChecksum", this.dataChecksum)
.add("compressedDataByteArray length", this.compressedDataByteArray.size()) .add("compressedDataByteArray length", this.compressedDataByteArray.size())
.add("compressedColumnGenStepByteArray length", this.compressedColumnGenStepByteArray.size()) .add("compressedColumnGenStepByteArray length", this.compressedColumnGenStepByteArray.size())
@@ -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.dto.IBaseDTO;
import com.seibel.distanthorizons.core.sql.repo.phantoms.AutoClosableTrackingWrapper; import com.seibel.distanthorizons.core.sql.repo.phantoms.AutoClosableTrackingWrapper;
import com.seibel.distanthorizons.core.util.KeyedLockContainer; import com.seibel.distanthorizons.core.util.KeyedLockContainer;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -501,7 +502,12 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
} }
else 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 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); ACTIVE_CONNECTION_STRINGS_BY_REPO.remove(this);
@@ -1,430 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Logger;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.function.Function;
/**
* Handles parsing and creating string messages from OpenGL messages.
*
* @author Leetom
* @version 2022-10-1
*/
public final class GLMessage
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
static final String HEADER = "[LWJGL] OpenGL debug message";
public final EType type;
public final ESeverity severity;
public final ESource source;
public final String id;
public final String message;
/** This is needed since gl callback will not have the correct class loader set, which causes issues. */
static void initLoadClass()
{
Builder dummy = new Builder();
dummy.add(GLMessage.HEADER);
dummy.add("ID");
dummy.add(":");
dummy.add("dummyId");
dummy.add("Source");
dummy.add(":");
dummy.add(ESource.API.name);
dummy.add("Type");
dummy.add(":");
dummy.add(EType.OTHER.name);
dummy.add("Severity");
dummy.add(":");
dummy.add(ESeverity.LOW.name);
dummy.add("Message");
dummy.add(":");
dummy.add("dummyMessage");
}
static
{
initLoadClass();
}
GLMessage(EType type, ESeverity severity, ESource source, String id, String message)
{
this.type = type;
this.source = source;
this.severity = severity;
this.id = id;
this.message = message;
}
@Override
public String toString() { return "[level:" + severity + ", type:" + type + ", source:" + source + ", id:" + id + ", msg:{" + message + "}]"; }
//==============//
// helper enums //
//==============//
public enum EType
{
ERROR,
DEPRECATED_BEHAVIOR,
UNDEFINED_BEHAVIOR,
PORTABILITY,
PERFORMANCE,
MARKER,
PUSH_GROUP,
POP_GROUP,
OTHER;
private static final HashMap<String, EType> ENUM_BY_NAME = new HashMap<>();
private final String name;
static
{
for (EType type : EType.values())
{
ENUM_BY_NAME.put(type.name, type);
}
}
EType() { name = super.toString().toUpperCase(); }
@Override
public final String toString() { return name; }
public static EType get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
}
public enum ESource
{
API,
WINDOW_SYSTEM,
SHADER_COMPILER,
THIRD_PARTY,
APPLICATION,
OTHER;
private static final HashMap<String, ESource> ENUM_BY_NAME = new HashMap<>();
public final String name;
static
{
for (ESource source : ESource.values())
{
ENUM_BY_NAME.put(source.name, source);
}
}
ESource() { name = super.toString().toUpperCase(); }
@Override
public final String toString() { return name; }
public static ESource get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
}
public enum ESeverity
{
HIGH,
MEDIUM,
LOW,
NOTIFICATION;
public final String name;
static final HashMap<String, ESeverity> ENUM_BY_NAME = new HashMap<>();
static
{
for (ESeverity severity : ESeverity.values())
{
ENUM_BY_NAME.put(severity.name, severity);
}
}
ESeverity() { name = super.toString().toUpperCase(); }
@Override
public final String toString() { return name; }
public static ESeverity get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
}
//================//
// helper classes //
//================//
/**
* Expected message format: <br>
* <code>
* [LWJGL] OpenGL debug message <br>
* ID: 0x20071 <br>
* Source: API <br>
* Type: OTHER <br>
* Severity: NOTIFICATION <br>
* Message: Buffer detailed info: Buffer object 1014084 (bound to ...
* </code>
*/
public static class Builder
{
/** how many stages are present in the message parser */
private static final int FINAL_PARSER_STAGE_INDEX = 15;
public static final Builder DEFAULT_MESSAGE_BUILDER =
new Builder(
(type) ->
{ // type filter
if (type == GLMessage.EType.POP_GROUP)
return false;
if (type == GLMessage.EType.PUSH_GROUP)
return false;
if (type == GLMessage.EType.MARKER)
return false;
// if (type == GLMessage.Type.PERFORMANCE) return false;
return true;
},
(severity) ->
{ // severity filter
if (severity == GLMessage.ESeverity.NOTIFICATION)
return false;
return true;
},
null
);
private final StringBuilder inProgressMessageBuilder = new StringBuilder();
private EType type;
private ESeverity severity;
private ESource source;
/** if the function returns false the message will be allowed */
private final Function<EType, Boolean> typeFilter;
/** if the function returns false the message will be allowed */
private final Function<ESeverity, Boolean> severityFilter;
/** if the function returns false the message will be allowed */
private final Function<ESource, Boolean> sourceFilter;
private String id;
private String message;
/** how far into the message parser this builder is */
private int parserStage = 0;
static
{
initLoadClass();
}
public Builder() { this(null, null, null); }
public Builder(
Function<EType, Boolean> typeFilter,
Function<ESeverity, Boolean> severityFilter,
Function<ESource, Boolean> sourceFilter)
{
this.typeFilter = typeFilter;
this.severityFilter = severityFilter;
this.sourceFilter = sourceFilter;
}
/**
* Adds the given string to the message builder. <br> <br>
*
* Will log a warning if the string given wasn't expected
* for the next stage of the OpenGL message format.<br> <br>
*
* @return null if the message isn't complete
*/
public GLMessage add(String str)
{
// TODO fix implementation for MC 1.20.2 and newer
// please see the incomplete GLMessageTest for an example as to how the message formats differ
if (true)
return null;
str = str.trim();
if (str.isEmpty())
return null;
boolean parseSuccess = runNextParserStage(str);
if (parseSuccess && parserStage > FINAL_PARSER_STAGE_INDEX)
{
this.parserStage = 0;
GLMessage msg = new GLMessage(this.type, this.severity, this.source, this.id, this.message);
if (doesMessagePassFilters(msg))
{
return msg;
}
}
else if (!parseSuccess)
{
LOGGER.warn("Failed to parse GLMessage line '{}' at stage {}", str, parserStage);
}
// the message isn't finished yet
return null;
// TODO implement a method that works for both MC 1.20.2+ and 1.20.1-
//if (str.equals(HEADER) && inProgressMessageBuilder.length() != 0)
//{
// boolean parseSuccess = runNextParserStage(str);
// if (parseSuccess && parserStage > FINAL_PARSER_STAGE_INDEX)
// {
// this.parserStage = 0;
// GLMessage msg = new GLMessage(this.type, this.severity, this.source, this.id, this.message);
// if (doesMessagePassFilters(msg))
// {
// return msg;
// }
// else
// {
// inProgressMessageBuilder.setLength(0);
// return null;
// }
// }
// else
// {
// if (!parseSuccess)
// {
// LOGGER.warn("Failed to parse GLMessage line '{}' at stage {}", str, parserStage);
// inProgressMessageBuilder.setLength(0);
// }
//
// return null;
// }
//}
//else
//{
// inProgressMessageBuilder.append(str);
// return null;
//}
}
private boolean doesMessagePassFilters(GLMessage msg)
{
if (this.sourceFilter != null && !this.sourceFilter.apply(msg.source))
return false;
else if (this.typeFilter != null && !this.typeFilter.apply(msg.type))
return false;
else if (this.severityFilter != null && !this.severityFilter.apply(msg.severity))
return false;
else
return true;
}
/** @return true if the given string was expected next for the OpenGL message format */
private boolean runNextParserStage(String str)
{
switch (this.parserStage)
{
case 0:
return checkAndIncStage(str, GLMessage.HEADER);
case 1:
return checkAndIncStage(str, "ID");
case 2:
return checkAndIncStage(str, ":");
case 3:
this.id = str;
this.parserStage++;
return true;
case 4:
return checkAndIncStage(str, "Source");
case 5:
return checkAndIncStage(str, ":");
case 6:
this.source = ESource.get(str);
this.parserStage++;
return true;
case 7:
return checkAndIncStage(str, "Type");
case 8:
return checkAndIncStage(str, ":");
case 9:
this.type = EType.get(str);
this.parserStage++;
return true;
case 10:
return checkAndIncStage(str, "Severity");
case 11:
return checkAndIncStage(str, ":");
case 12:
this.severity = ESeverity.get(str);
this.parserStage++;
return true;
case 13:
return checkAndIncStage(str, "Message");
case 14:
return checkAndIncStage(str, ":");
case 15:
this.message = str;
this.parserStage++;
return true;
default:
return false;
}
}
/**
* Returns true and increments the parserStage
* if the given and expected strings are the same.
*/
private boolean checkAndIncStage(String givenString, String expectedString)
{
boolean equal = givenString.equals(expectedString);
//boolean equal = givenString.contains(expectedString);
if (equal)
this.parserStage++;
return equal;
}
} // builder class
}
@@ -0,0 +1,54 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects.GLMessages;
import java.util.HashMap;
public enum EGLMessageSeverity
{
HIGH,
MEDIUM,
LOW,
NOTIFICATION;
public final String name;
static final HashMap<String, EGLMessageSeverity> ENUM_BY_NAME = new HashMap<>();
static
{
for (EGLMessageSeverity severity : EGLMessageSeverity.values())
{
ENUM_BY_NAME.put(severity.name, severity);
}
}
EGLMessageSeverity() { this.name = super.toString().toUpperCase(); }
@Override
public final String toString() { return this.name; }
public static EGLMessageSeverity get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
}
@@ -0,0 +1,55 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects.GLMessages;
import java.util.HashMap;
public enum EGLMessageSource
{
API,
WINDOW_SYSTEM,
SHADER_COMPILER,
THIRD_PARTY,
APPLICATION,
OTHER;
private static final HashMap<String, EGLMessageSource> ENUM_BY_NAME = new HashMap<>();
public final String name;
static
{
for (EGLMessageSource source : EGLMessageSource.values())
{
ENUM_BY_NAME.put(source.name, source);
}
}
EGLMessageSource() { this.name = super.toString().toUpperCase(); }
@Override
public final String toString() { return this.name; }
public static EGLMessageSource get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
}
@@ -0,0 +1,58 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects.GLMessages;
import java.util.HashMap;
public enum EGLMessageType
{
ERROR,
DEPRECATED_BEHAVIOR,
UNDEFINED_BEHAVIOR,
PORTABILITY,
PERFORMANCE,
MARKER,
PUSH_GROUP,
POP_GROUP,
OTHER;
private static final HashMap<String, EGLMessageType> ENUM_BY_NAME = new HashMap<>();
public final String name;
static
{
for (EGLMessageType type : EGLMessageType.values())
{
ENUM_BY_NAME.put(type.name, type);
}
}
EGLMessageType() { this.name = super.toString().toUpperCase(); }
@Override
public final String toString() { return this.name; }
public static EGLMessageType get(String name) { return ENUM_BY_NAME.get(name.toUpperCase()); }
}
@@ -0,0 +1,56 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects.GLMessages;
public final class GLMessage
{
static final String HEADER = "[LWJGL] OpenGL debug message";
public final EGLMessageType type;
public final EGLMessageSeverity severity;
public final EGLMessageSource source;
public final String id;
public final String message;
GLMessage(EGLMessageType type, EGLMessageSeverity severity, EGLMessageSource source, String id, String message)
{
this.type = type;
this.source = source;
this.severity = severity;
this.id = id;
this.message = message;
}
@Override
public String toString()
{
return "level: [" + this.severity + "], " +
"type: [" + this.type + "], " +
"source: [" + this.source + "], " +
"id: [" + this.id + "], " +
"msg: [" + this.message + "]";
}
}
@@ -0,0 +1,319 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects.GLMessages;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.apache.logging.log4j.Logger;
import java.util.function.Consumer;
import java.util.function.Function;
/** Expected message formats can be found in GLMessageTest. */
public class GLMessageBuilder
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/** how many stages are present in the message parser */
private static final int FINAL_LEGACY_PARSER_STAGE_INDEX = 15;
private static final int FINAL_NEW_PARSER_STAGE_INDEX = 5;
private EGLMessageType type;
private EGLMessageSeverity severity;
private EGLMessageSource source;
/** if the function returns false the message will be allowed */
private final Function<EGLMessageType, Boolean> typeFilter;
/** if the function returns false the message will be allowed */
private final Function<EGLMessageSeverity, Boolean> severityFilter;
/** if the function returns false the message will be allowed */
private final Function<EGLMessageSource, Boolean> sourceFilter;
private String id;
private String message;
/** how far into the message parser this builder is */
private int parserStage = 0;
private boolean legacyMessage = true;
//==============//
// constructors //
//==============//
public GLMessageBuilder() { this(null, null, null); }
public GLMessageBuilder(
Function<EGLMessageType, Boolean> typeFilter,
Function<EGLMessageSeverity, Boolean> severityFilter,
Function<EGLMessageSource, Boolean> sourceFilter)
{
this.typeFilter = typeFilter;
this.severityFilter = severityFilter;
this.sourceFilter = sourceFilter;
}
//=================//
// message parsing //
//=================//
/**
* Adds the given string to the message builder. <br> <br>
*
* Will log a warning if the string given wasn't expected
* for the next stage of the OpenGL message format.<br> <br>
*
* @return null if the message isn't complete
*/
public GLMessage add(String str)
{
str = str.trim();
if (str.isEmpty())
{
return null;
}
boolean messageFinished = false;
boolean parseSuccess = this.runNextParserStage(str);
if (this.legacyMessage
&& this.parserStage > FINAL_LEGACY_PARSER_STAGE_INDEX)
{
messageFinished = true;
}
else if (!this.legacyMessage
&& this.parserStage > FINAL_NEW_PARSER_STAGE_INDEX)
{
messageFinished = true;
}
if (parseSuccess && messageFinished)
{
this.parserStage = 0;
GLMessage msg = new GLMessage(this.type, this.severity, this.source, this.id, this.message);
if (this.doesMessagePassFilters(msg))
{
return msg;
}
}
else if (!parseSuccess && messageFinished)
{
LOGGER.warn("Failed to parse GLMessage line [" + str + "] at stage [" + this.parserStage + "]");
}
// the message isn't finished yet
return null;
}
private boolean doesMessagePassFilters(GLMessage msg)
{
if (this.sourceFilter != null && !this.sourceFilter.apply(msg.source))
return false;
else if (this.typeFilter != null && !this.typeFilter.apply(msg.type))
return false;
else if (this.severityFilter != null && !this.severityFilter.apply(msg.severity))
return false;
else
return true;
}
/** @return true if the given string was expected next for the OpenGL message format */
private boolean runNextParserStage(String str)
{
if (this.parserStage == 0)
{
return this.checkExactAndIncStage(str, GLMessage.HEADER);
}
else if (this.parserStage == 1)
{
// legacy message only contains "ID" (not the colon)
this.legacyMessage = !str.contains("ID: ");
}
if (this.legacyMessage)
{
return this.runNextLegacyParserStage(str);
}
else
{
return this.runNextNewParserStage(str);
}
}
/** MC 1.20.2 and older */
private boolean runNextLegacyParserStage(String str)
{
switch (this.parserStage)
{
case 0:
throw new IllegalStateException("Parser should be past stage ["+this.parserStage+"], next stage is [1].");
case 1:
return this.checkExactAndIncStage(str, "ID");
case 2:
return this.checkExactAndIncStage(str, ":");
case 3:
this.id = str;
this.parserStage++;
return true;
case 4:
return this.checkExactAndIncStage(str, "Source");
case 5:
return this.checkExactAndIncStage(str, ":");
case 6:
this.source = EGLMessageSource.get(str);
this.parserStage++;
return true;
case 7:
return this.checkExactAndIncStage(str, "Type");
case 8:
return this.checkExactAndIncStage(str, ":");
case 9:
this.type = EGLMessageType.get(str);
this.parserStage++;
return true;
case 10:
return this.checkExactAndIncStage(str, "Severity");
case 11:
return this.checkExactAndIncStage(str, ":");
case 12:
this.severity = EGLMessageSeverity.get(str);
this.parserStage++;
return true;
case 13:
return this.checkExactAndIncStage(str, "Message");
case 14:
return this.checkExactAndIncStage(str, ":");
case 15:
this.message = str;
this.parserStage++;
return true;
default:
return false;
}
}
/** after MC 1.20.2 */
private boolean runNextNewParserStage(String str)
{
switch (this.parserStage)
{
case 0:
throw new IllegalStateException("Parser should be past stage [" + this.parserStage + "], next stage is [1].");
case 1:
String idPrefix = "ID: ";
return this.checkPrefixAndRun(str, idPrefix,
(line) ->
{
this.id = trySubstring(str, idPrefix.length());
this.parserStage++;
});
case 2:
String sourcePrefix = "Source: ";
return this.checkPrefixAndRun(str, sourcePrefix,
(line) ->
{
String sourceString = trySubstring(str, sourcePrefix.length());
this.source = EGLMessageSource.get(sourceString);
this.parserStage++;
});
case 3:
String typePrefix = "Type: ";
return this.checkPrefixAndRun(str, typePrefix,
(line) ->
{
String sourceString = trySubstring(str, typePrefix.length());
this.type = EGLMessageType.get(sourceString);
this.parserStage++;
});
case 4:
String severityPrefix = "Severity: ";
return this.checkPrefixAndRun(str, severityPrefix,
(line) ->
{
String sourceString = trySubstring(str, severityPrefix.length());
this.severity = EGLMessageSeverity.get(sourceString);
this.parserStage++;
});
case 5:
String messagePrefix = "Message: ";
return this.checkPrefixAndRun(str, messagePrefix,
(line) ->
{
this.message = trySubstring(str, messagePrefix.length());
this.parserStage++;
});
default:
return false;
}
}
//================//
// helper methods //
//================//
/**
* Returns true and increments the parserStage
* if the message and expected strings are the same.
*/
private boolean checkExactAndIncStage(String message, String expectedString)
{
boolean equal = message.equals(expectedString);
if (equal)
{
this.parserStage++;
}
return equal;
}
/**
* Returns true and increments the parserStage
* if the message starts with the given prefix.
*/
private boolean checkPrefixAndRun(String message, String expectedPrefix, Consumer<String> successConsumer)
{
boolean equal = message.startsWith(expectedPrefix);
if (equal)
{
successConsumer.accept(message);
}
return equal;
}
/** returns "" if the string isn't long enough */
private static String trySubstring(String string, int beginIndex)
{
if (beginIndex > string.length())
{
// prevent index-out-of-bounds errors
// if the message isn't what we expected
return "";
}
return string.substring(beginIndex);
}
}
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.seibel.distanthorizons.core.util.objects; package com.seibel.distanthorizons.core.util.objects.GLMessages;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@@ -27,12 +27,12 @@ import java.util.function.Consumer;
public final class GLMessageOutputStream extends OutputStream public final class GLMessageOutputStream extends OutputStream
{ {
final Consumer<GLMessage> func; final Consumer<GLMessage> func;
final GLMessage.Builder builder; final GLMessageBuilder builder;
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
public GLMessageOutputStream(Consumer<GLMessage> func, GLMessage.Builder builder) public GLMessageOutputStream(Consumer<GLMessage> func, GLMessageBuilder builder)
{ {
this.func = func; this.func = func;
this.builder = builder; this.builder = builder;
@@ -41,24 +41,30 @@ public final class GLMessageOutputStream extends OutputStream
@Override @Override
public void write(int b) public void write(int b)
{ {
buffer.write(b); this.buffer.write(b);
if (b == '\n') flush(); if (b == '\n')
{
this.flush();
}
} }
@Override @Override
public void flush() public void flush()
{ {
String str = buffer.toString(); String str = this.buffer.toString();
GLMessage msg = builder.add(str); GLMessage msg = this.builder.add(str);
if (msg != null) func.accept(msg); if (msg != null)
buffer.reset(); {
this.func.accept(msg);
}
this.buffer.reset();
} }
@Override @Override
public void close() throws IOException public void close() throws IOException
{ {
flush(); this.flush();
buffer.close(); this.buffer.close();
} }
} }
@@ -78,9 +78,11 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor
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 / 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() public static void setupThreadPools()
{ {
// thread pools //==================//
// main thread pool //
//==================//
if (taskPicker != null)
{
taskPicker.shutdown();
}
taskPicker = new PriorityTaskPicker(); taskPicker = new PriorityTaskPicker();
networkCompressionThreadPool = taskPicker.createExecutor(); networkCompressionThreadPool = taskPicker.createExecutor();
@@ -98,8 +105,22 @@ public class ThreadPoolUtil
updatePropagatorThreadPool = taskPicker.createExecutor(); updatePropagatorThreadPool = taskPicker.createExecutor();
worldGenThreadPool = taskPicker.createExecutor(); worldGenThreadPool = taskPicker.createExecutor();
// single thread pools
//=========================//
// standalone thread pools //
//=========================//
if (beaconCullingThreadPool != null)
{
beaconCullingThreadPool.shutdown();
}
beaconCullingThreadPool = ThreadUtil.makeSingleThreadPool(BEACON_CULLING_THREAD_NAME); beaconCullingThreadPool = ThreadUtil.makeSingleThreadPool(BEACON_CULLING_THREAD_NAME);
if (fullDataMigrationThreadPool != null)
{
fullDataMigrationThreadPool.shutdown();
}
fullDataMigrationThreadPool = ThreadUtil.makeSingleThreadPool(FULL_DATA_MIGRATION_THREAD_NAME); 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 ClientOnlySaveStructure saveStructure;
public final ClientNetworkState networkState = new ClientNetworkState(); public final ClientNetworkState networkState = new ClientNetworkState();
public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client World Ticker Thread"); public final ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client World Ticker Thread");
public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); 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.networkState.close();
this.dhTickerThread.shutdownNow();
for (DhClientLevel dhClientLevel : this.levels.values()) for (DhClientLevel dhClientLevel : this.levels.values())
{ {
@@ -37,6 +37,7 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
} }
//================// //================//
// level handling // // level handling //
//================// //================//
@@ -23,9 +23,11 @@ import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.Closeable;
import java.util.concurrent.CompletableFuture; 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); IDhLevel getOrLoadLevel(@NotNull ILevelWrapper levelWrapper);
@@ -49,6 +49,11 @@ public interface IBlockStateWrapper extends IDhApiBlockStateWrapper
boolean isBeaconBlock(); boolean isBeaconBlock();
/** IE a glass block that can affect the beacon beam color */ /** IE a glass block that can affect the beacon beam color */
boolean isBeaconTintBlock(); 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 * The blocks used by a beacon's base
* IE Iron, diamond, gold, etc. * IE Iron, diamond, gold, etc.
@@ -381,8 +381,9 @@ public interface IChunkWrapper extends IBindable
for (int y = beaconRelPos.getY() +1; y <= maxY; y++) for (int y = beaconRelPos.getY() +1; y <= maxY; y++)
{ {
IBlockStateWrapper block = centerChunk.getBlockState(beaconRelPos.getX(), y, beaconRelPos.getZ()); 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; return null;
} }
@@ -60,6 +60,9 @@ public interface IMinecraftRenderWrapper extends IBindable
int getScreenWidth(); int getScreenWidth();
int getScreenHeight(); int getScreenHeight();
boolean mcRendersToFrameBuffer();
boolean runningLegacyOpenGL();
/** @return -1 if no valid framebuffer is available yet */ /** @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) 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 // Iris commit: https://github.com/IrisShaders/Iris/commit/a76a240527e93780bbcba57c09bef377419d47a7#diff-7b9ded0c79bbcdb130010373387756a28ee8d3640d522c0a5b7acd0abbfc20aeR16
@@ -477,7 +477,11 @@
"distanthorizons.config.client.advanced.debugging.openGl.overrideVanillaGLLogger": "distanthorizons.config.client.advanced.debugging.openGl.overrideVanillaGLLogger":
"Override Vanilla GL Logger", "Override Vanilla GL Logger",
"distanthorizons.config.client.advanced.debugging.openGl.overrideVanillaGLLogger.@tooltip": "distanthorizons.config.client.advanced.debugging.openGl.overrideVanillaGLLogger.@tooltip":
"Defines how OpenGL errors are handled. \n Requires rebooting Minecraft to apply. \nMay incorrectly catch OpenGL errors thrown by other mods.", "Defines how OpenGL errors are handled. \nRequires rebooting Minecraft to change. \nWill catch OpenGL errors thrown by other mods.",
"distanthorizons.config.client.advanced.debugging.openGl.onlyLogGlErrorsOnce":
"Only Log GL Errors Once",
"distanthorizons.config.client.advanced.debugging.openGl.onlyLogGlErrorsOnce.@tooltip":
"If true each Open GL error will only be logged once. \nTEnabling this may cause some error logs to be missed. \nDoes nothing if overrideVanillaGLLogger is set to false. \n\nGenerally this can be kept as 'true' to prevent log spam. \nHowever, Please set this to 'false' if a developer needs your log to debug a GL issue. \n",
"distanthorizons.config.client.advanced.debugging.openGl.glErrorHandlingMode": "distanthorizons.config.client.advanced.debugging.openGl.glErrorHandlingMode":
"OpenGL Error Handling Mode", "OpenGL Error Handling Mode",
"distanthorizons.config.client.advanced.debugging.openGl.glErrorHandlingMode.@tooltip": "distanthorizons.config.client.advanced.debugging.openGl.glErrorHandlingMode.@tooltip":
+13 -4
View File
@@ -8,17 +8,26 @@ uniform sampler2D gDhColorTexture;
uniform sampler2D gDhDepthTexture; uniform sampler2D gDhDepthTexture;
/**
* LOD application shader
*
* This merges the rendered LODs into Minecraft's texture/FBO
*/
void main() void main()
{ {
fragColor = vec4(0.0); fragColor = vec4(0.0);
float fragmentDepth = texture(gDhDepthTexture, TexCoord).r;
// a fragment depth of "1" means the fragment wasn't drawn to, // a fragment depth of "1" means the fragment wasn't drawn to,
// only update fragments that were 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); 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; uniform sampler2D uDepthTexture;
/**
* Fog application shader
*
* This merges the rendered fog onto DH's rendered LODs
*/
void main() void main()
{ {
fragColor = vec4(1.0); fragColor = vec4(0.0);
float fragmentDepth = textureLod(uDepthTexture, TexCoord, 0).r;
// a fragment depth of "1" means the fragment wasn't drawn to, // a fragment depth of "1" means the fragment wasn't drawn to,
// only update fragments that were 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); fragColor = texture(uColorTexture, TexCoord);
} }
+10 -2
View File
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.sql.DatabaseUpdater; import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo; import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.sql.repo.phantoms.AutoClosableTrackingWrapper;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.junit.Assert; import org.junit.Assert;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@@ -218,6 +219,12 @@ public class DhRepoSqliteTest
@Test @Test
public void testRepoLeakDetection() public void testRepoLeakDetection()
{ {
if (!AutoClosableTrackingWrapper.TRACK_WRAPPERS)
{
System.out.println("Skipping repo leak detection unit test. Leak tracking is disabled.");
return;
}
TestPrimaryKeyRepo primaryKeyRepo = null; TestPrimaryKeyRepo primaryKeyRepo = null;
try try
{ {
@@ -298,7 +305,8 @@ public class DhRepoSqliteTest
} }
} }
//Assert.assertNotEquals(0, primaryKeyRepo.openClosables.size()); // TODO fails when built for release due to tracking being disabled // TODO fails when built for release due to tracking being disabled
Assert.assertNotEquals(0, primaryKeyRepo.openClosables.size());
primaryKeyRepo.openClosables.clear(); primaryKeyRepo.openClosables.clear();
@@ -318,7 +326,7 @@ public class DhRepoSqliteTest
} }
} }
//Assert.assertNotEquals(0, primaryKeyRepo.openClosables.size()); Assert.assertNotEquals(0, primaryKeyRepo.openClosables.size());
} }
} }
catch (SQLException e) catch (SQLException e)
+46 -22
View File
@@ -19,7 +19,7 @@
package tests; package tests;
import com.seibel.distanthorizons.core.util.objects.GLMessage; import com.seibel.distanthorizons.core.util.objects.GLMessages.*;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@@ -28,66 +28,88 @@ import java.util.ArrayList;
public class GLMessageTest public class GLMessageTest
{ {
public static final String MESSAGE_ID = "0x20071"; public static final String MESSAGE_ID = "0x20071";
public static final GLMessage.ESource MESSAGE_SOURCE = GLMessage.ESource.API; public static final EGLMessageSource MESSAGE_SOURCE = EGLMessageSource.API;
public static final GLMessage.EType MESSAGE_TYPE = GLMessage.EType.OTHER; public static final EGLMessageType MESSAGE_TYPE = EGLMessageType.OTHER;
public static final GLMessage.ESeverity MESSAGE_SEVERITY = GLMessage.ESeverity.NOTIFICATION; public static final EGLMessageSeverity MESSAGE_SEVERITY = EGLMessageSeverity.NOTIFICATION;
public static final String MESSAGE = "Buffer detailed info: Buffer object 1014084 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as\" \"the source for buffer object operations."; public static final String MESSAGE = "Buffer detailed info: Buffer object 1014084 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as\" \"the source for buffer object operations.";
/** This is how debug messages were sent prior to Minecraft 1.20.2 */ /** This is how debug messages were sent prior to Minecraft 1.20.2 */
private static final String[] PRE_1_20_2_MESSAGE_ARRAY = private static final String[] OLD_MESSAGE_ARRAY =
{ {
"[LWJGL] OpenGL debug message" "[LWJGL] OpenGL debug message"
,"ID", ":", "0x20071" ,"ID", ":", MESSAGE_ID
,"Source", ":", "API" ,"Source", ":", MESSAGE_SOURCE.name
,"Type", ":", "OTHER" ,"Type", ":", MESSAGE_TYPE.name
,"Severity", ":", "NOTIFICATION" ,"Severity", ":", MESSAGE_SEVERITY.name
,"Message", ":", "Buffer detailed info: Buffer object 1014084 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as\" \"the source for buffer object operations." ,"Message", ":", MESSAGE
// optional addition to force the builder into noticing the message ended, shouldn't be necessary // optional addition to force the builder into noticing the message ended, shouldn't be necessary
//,"[LWJGL] OpenGL debug message" //,"[LWJGL] OpenGL debug message"
}; };
/** This is how debug messages were sent after (and including) Minecraft 1.20.2 */ /** This is how debug messages were sent after (and including) Minecraft 1.20.2 */
private static final String[] POST_1_20_2_MESSAGE_ARRAY = private static final String[] NEW_MESSAGE_ARRAY =
{ {
"[LWJGL] OpenGL debug message" "[LWJGL] OpenGL debug message"
,"ID: 0x20071" ,"ID: " + MESSAGE_ID
,"Source: API" ,"Source: " + MESSAGE_SOURCE.name
,"Type: OTHER" ,"Type: " + MESSAGE_TYPE.name
,"Severity: NOTIFICATION" ,"Severity: " + MESSAGE_SEVERITY.name
,"Message: Buffer detailed info: Buffer object 1014084 (bound to GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as\" \"the source for buffer object operations." ,"Message: " + MESSAGE
// optional addition to force the builder into noticing the message ended, shouldn't be necessary // optional addition to force the builder into noticing the message ended, shouldn't be necessary
//,"[LWJGL] OpenGL debug message" //,"[LWJGL] OpenGL debug message"
}; };
public final GLMessageBuilder messageBuilder = new GLMessageBuilder(null, null, null);
//=======//
// tests //
//=======//
@Test @Test
public void preMc1_20_2() public void preMc1_20_2()
{ {
ArrayList<GLMessage> messageList = new ArrayList<>(); ArrayList<GLMessage> messageList = new ArrayList<>();
for (String str : PRE_1_20_2_MESSAGE_ARRAY) for (String str : OLD_MESSAGE_ARRAY)
{ {
GLMessage message = GLMessage.Builder.DEFAULT_MESSAGE_BUILDER.add(str); GLMessage message = this.messageBuilder.add(str);
if (message != null) if (message != null)
{ {
messageList.add(message); messageList.add(message);
} }
} }
//Assert.assertEquals("Incorrect message parse count.", 1, messageList.size()); Assert.assertEquals("Incorrect message parse count.", 1, messageList.size());
//testMessage(messageList.get(0)); messageMatchesExpected(messageList.get(0));
} }
@Test @Test
public void mc1_20_2() public void mc1_20_2()
{ {
// TODO ArrayList<GLMessage> messageList = new ArrayList<>();
for (String str : NEW_MESSAGE_ARRAY)
{
GLMessage message = this.messageBuilder.add(str);
if (message != null)
{
messageList.add(message);
}
}
Assert.assertEquals("Incorrect message parse count.", 1, messageList.size());
messageMatchesExpected(messageList.get(0));
} }
//================//
// helper methods //
//================//
private static void messageMatchesExpected(GLMessage testMessage) private static void messageMatchesExpected(GLMessage testMessage)
{ {
Assert.assertEquals(MESSAGE_ID, testMessage.id); Assert.assertEquals(MESSAGE_ID, testMessage.id);
@@ -97,4 +119,6 @@ public class GLMessageTest
Assert.assertEquals(MESSAGE, testMessage.message); Assert.assertEquals(MESSAGE, testMessage.message);
} }
} }