diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index 68ff786bf..15abd43d7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -28,8 +28,10 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGenerat import com.seibel.distanthorizons.core.config.eventHandlers.QuickRenderToggleConfigEventHandler; import com.seibel.distanthorizons.core.config.eventHandlers.RenderCacheConfigEventHandler; import com.seibel.distanthorizons.core.config.eventHandlers.UnsafeValuesConfigListener; +import com.seibel.distanthorizons.core.config.eventHandlers.WorldCurvatureConfigEventHandler; import com.seibel.distanthorizons.core.config.eventHandlers.presets.ThreadPresetConfigEventHandler; import com.seibel.distanthorizons.core.config.eventHandlers.presets.RenderQualityPresetConfigEventHandler; +import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.config.types.*; import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance; import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryPerformance; @@ -575,6 +577,7 @@ public class Config + "Note: Due to current limitations, the min value is 50 \n" + "and the max value is 5000. Any values outside this range \n" + "will be set to 0 (disabled).") + .addListener(WorldCurvatureConfigEventHandler.INSTANCE) .build(); public static ConfigEntry lodBias = new ConfigEntry.Builder() @@ -734,6 +737,7 @@ public class Config + "") .build(); + // not currently implemented public static ConfigEntry enableMultiverseNetworking = new ConfigEntry.Builder() .set(true) .comment("" @@ -742,6 +746,7 @@ public class Config + "") .build(); + // not currently implemented public static ConfigEntry enableServerNetworking = new ConfigEntry.Builder() .set(false) .comment("" diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/WorldCurvatureConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/WorldCurvatureConfigEventHandler.java new file mode 100644 index 000000000..0fdea7e48 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/WorldCurvatureConfigEventHandler.java @@ -0,0 +1,47 @@ +package com.seibel.distanthorizons.core.config.eventHandlers; + +import com.seibel.distanthorizons.api.DhApi; +import com.seibel.distanthorizons.api.enums.config.ELodShading; +import com.seibel.distanthorizons.api.enums.config.EMaxHorizontalResolution; +import com.seibel.distanthorizons.api.enums.config.EVerticalQuality; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.config.listeners.IConfigListener; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * Listens to the config and will automatically + * clear the current render cache if certain settings are changed.

+ * + * Note: if additional settings should clear the render cache, add those to this listener, don't create a new listener + */ +public class WorldCurvatureConfigEventHandler implements IConfigListener +{ + public static WorldCurvatureConfigEventHandler INSTANCE = new WorldCurvatureConfigEventHandler(); + + private static final int MIN_VALID_CURVE_VALUE = 50; + + + /** private since we only ever need one handler at a time */ + private WorldCurvatureConfigEventHandler() { } + + + + @Override + public void onConfigValueSet() + { + int curveRatio = Config.Client.Advanced.Graphics.AdvancedGraphics.earthCurveRatio.get(); + if (curveRatio > 0 && curveRatio < MIN_VALID_CURVE_VALUE) + { + // shouldn't update the UI, otherwise we may end up fighting the user + Config.Client.Advanced.Graphics.AdvancedGraphics.earthCurveRatio.set(MIN_VALID_CURVE_VALUE); + } + + } + + @Override + public void onUiModify() { /* do nothing, we only care about modified config values */ } + + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java index 1248de29d..bf50944cf 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/metaData/AbstractMetaDataContainerFile.java @@ -215,16 +215,19 @@ public abstract class AbstractMetaDataContainerFile { fileChannel.position(METADATA_SIZE_IN_BYTES); - try (DhDataOutputStream compressedOut = new DhDataOutputStream(Channels.newOutputStream(fileChannel)); - CheckedOutputStream checkedOut = new CheckedOutputStream(compressedOut, new Adler32())) // TODO: Is Adler32 ok? - { - dataWriterFunc.writeBufferToFile(compressedOut); - this.baseMetaData.checksum = (int) checkedOut.getChecksum().getValue(); - } + // the order of these streams is important, otherwise the checksum won't be calculated + CheckedOutputStream checkedOut = new CheckedOutputStream(Channels.newOutputStream(fileChannel), new Adler32()); + // normally a DhStream should be the topmost stream to prevent closing the stream accidentally, but since this stream will be closed immediately after writing anyway, it won't be an issue + DhDataOutputStream compressedOut = new DhDataOutputStream(checkedOut); + + // write the contained data + dataWriterFunc.writeBufferToFile(compressedOut); + compressedOut.flush(); + this.baseMetaData.checksum = (int) checkedOut.getChecksum().getValue(); - fileChannel.position(0); // Write metadata + fileChannel.position(0); ByteBuffer buffer = ByteBuffer.allocate(METADATA_SIZE_IN_BYTES); buffer.putInt(METADATA_IDENTITY_BYTES); buffer.putInt(this.pos.sectionX); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/IWorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/IWorldGenerationQueue.java index f3d2da864..e1aa2e376 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/IWorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/IWorldGenerationQueue.java @@ -18,6 +18,9 @@ public interface IWorldGenerationQueue extends Closeable void runCurrentGenTasksUntilBusy(DhBlockPos2D targetPos); + int getWaitingTaskCount(); + int getInProgressTaskCount(); + CompletableFuture startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning); void close(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java index 78c8da695..53260b68e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java @@ -18,6 +18,7 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.objects.DhThreadFactory; +import com.seibel.distanthorizons.core.util.objects.RateLimitedThreadPoolExecutor; import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; @@ -34,7 +35,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - public static final DhThreadFactory THREAD_FACTORY = new DhThreadFactory(ThreadUtil.THREAD_NAME_PREFIX + "Gen-Worker-Thread", Thread.MIN_PRIORITY); + public static final DhThreadFactory THREAD_FACTORY = new DhThreadFactory(ThreadUtil.THREAD_NAME_PREFIX + "World-Gen-Worker-Thread", Thread.MIN_PRIORITY); private final IDhApiWorldGenerator generator; @@ -74,7 +75,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender private final HashMap alreadyGeneratedPosHashSet = new HashMap<>(MAX_ALREADY_GENERATED_COUNT); private final Queue alreadyGeneratedPosQueue = new LinkedList<>(); - private static ExecutorService worldGeneratorThreadPool; + private static RateLimitedThreadPoolExecutor worldGeneratorThreadPool; private static ConfigChangeListener configListener; @@ -533,6 +534,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender } worldGeneratorThreadPool = ThreadUtil.makeRateLimitedThreadPool(threadPoolSize, THREAD_FACTORY, Config.Client.Advanced.MultiThreading.runTimeRatioForWorldGenerationThreads); + worldGeneratorThreadPool.setOnTerminatedEventHandler(WorldGenerationQueue::onWorldGenThreadPoolTerminated); } /** @@ -548,6 +550,20 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender } } + private static void onWorldGenThreadPoolTerminated() + { + LOGGER.debug("World generator thread pool terminated. Suggesting the JVM runs a garbage collection to clean up any loose world generation objects..."); + System.gc(); + } + + + + //=========// + // getters // + //=========// + + public int getWaitingTaskCount() { return this.waitingTasks.size(); } + public int getInProgressTaskCount() { return this.inProgressGenTasksByLodPos.size(); } //==========// @@ -681,6 +697,11 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender } + + //=======// + // debug // + //=======// + @Override public void debugRender(DebugRenderer r) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java index 1fc7fd7a8..5183e8006 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java @@ -44,7 +44,6 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug private final ConcurrentMap waitingTasks = new ConcurrentHashMap<>(); private final Semaphore pendingTasksSemaphore = new Semaphore(Short.MAX_VALUE, true); - private int pendingTasks() { return Short.MAX_VALUE - pendingTasksSemaphore.availablePermits(); } private CompletableFuture genTaskPriorityRequest = CompletableFuture.completedFuture(null); private final Semaphore genTaskPriorityRequestSemaphore = new Semaphore(1, true); @@ -87,8 +86,8 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug { if (generatorClosingFuture != null || !networkState.getClient().isReady()) return; - while (waitingTasks.size() > pendingTasks() - && pendingTasks() < this.networkState.config.fullDataRequestRateLimit + while (getWaitingTaskCount() > getInProgressTaskCount() + && getInProgressTaskCount() < this.networkState.config.fullDataRequestRateLimit && pendingTasksSemaphore.tryAcquire()) { sendNewRequest(targetPos); @@ -224,11 +223,15 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug { ArrayList lines = new ArrayList<>(); lines.add("World Remote Generation Queue ["+level.getClientLevelWrapper().getDimensionType().getDimensionName()+"]"); - lines.add(" Requests: "+this.finishedRequests+" / "+(this.waitingTasks.size() + this.finishedRequests.get())+" (failed: "+ this.failedRequests+")"); - lines.add(" Pending: "+this.pendingTasks()+" / "+this.networkState.config.fullDataRequestRateLimit); + lines.add("Requests: "+this.finishedRequests+" / "+(this.getWaitingTaskCount() + this.finishedRequests.get())+" (failed: "+ this.failedRequests+", rate limit: "+this.networkState.config.fullDataRequestRateLimit+")"); return lines.toArray(new String[0]); } + @Override + public int getWaitingTaskCount() { return this.waitingTasks.size(); } + @Override + public int getInProgressTaskCount() { return Short.MAX_VALUE - pendingTasksSemaphore.availablePermits(); } + @Override public CompletableFuture startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java index 1d4dc68f8..34ab6ae82 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/ClientLevelModule.java @@ -1,17 +1,14 @@ package com.seibel.distanthorizons.core.level; import com.seibel.distanthorizons.api.enums.rendering.EDebugRendering; -import com.seibel.distanthorizons.core.config.AppliedConfigState; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.distanthorizons.core.file.renderfile.RenderSourceFileHandler; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; -import com.seibel.distanthorizons.core.generation.WorldRemoteGenerationQueue; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.f3.F3Screen; -import com.seibel.distanthorizons.core.network.NetworkClient; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhLodPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; @@ -22,31 +19,31 @@ import com.seibel.distanthorizons.core.render.renderer.LodRenderer; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; -import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.coreapi.util.math.Mat4f; import org.apache.logging.log4j.Logger; +import java.io.Closeable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; -public class ClientLevelModule +public class ClientLevelModule implements Closeable { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private final IDhClientLevel parent; public final AtomicReference ClientRenderStateRef = new AtomicReference<>(); public final F3Screen.NestedMessage f3Message; - public ClientLevelModule(IDhClientLevel parent) { - this.parent = parent; this.f3Message = new F3Screen.NestedMessage(this::f3Log); + this.parent = parent; + this.f3Message = new F3Screen.NestedMessage(this::f3Log); } - + //==============// // tick methods // //==============// - + private EDebugRendering lastDebugRendering = EDebugRendering.OFF; public void clientTick() @@ -56,8 +53,9 @@ public class ClientLevelModule { return; } - - ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); if (clientRenderState == null) + + ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); + if (clientRenderState == null) { return; } @@ -70,40 +68,50 @@ public class ClientLevelModule return; } - clientRenderState.close(); clientRenderState = new ClientRenderState(parent, parent.getFileHandler(), parent.getSaveStructure()); if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState)) - { - //FIXME: How to handle this? - LOGGER.warn("Failed to set render state due to concurrency after changing view distance"); clientRenderState.close(); return; + clientRenderState.close(); + clientRenderState = new ClientRenderState(parent, parent.getFileHandler(), parent.getSaveStructure()); + if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState)) + { + //FIXME: How to handle this? + LOGGER.warn("Failed to set render state due to concurrency after changing view distance"); + clientRenderState.close(); + return; + } } - } clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); - - boolean isBuffersDirty = false; EDebugRendering newDebugRendering = Config.Client.Advanced.Debugging.debugRendering.get(); + clientRenderState.quadtree.tick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); + + boolean isBuffersDirty = false; + EDebugRendering newDebugRendering = Config.Client.Advanced.Debugging.debugRendering.get(); if (newDebugRendering != lastDebugRendering) { - lastDebugRendering = newDebugRendering; isBuffersDirty = true; + lastDebugRendering = newDebugRendering; + isBuffersDirty = true; } if (isBuffersDirty) { clientRenderState.renderer.bufferHandler.MarkAllBuffersDirty(); } } - - + + //========// // render // //========// - + /** @return if the {@link ClientRenderState} was successfully swapped */ public boolean startRenderer() { - ClientRenderState ClientRenderState = new ClientRenderState(parent, parent.getFileHandler(), parent.getSaveStructure()); if (!this.ClientRenderStateRef.compareAndSet(null, ClientRenderState)) - { - LOGGER.warn("Failed to start renderer due to concurrency"); ClientRenderState.close(); return false; - } - else - { - return true; - } + ClientRenderState ClientRenderState = new ClientRenderState(parent, parent.getFileHandler(), parent.getSaveStructure()); + if (!this.ClientRenderStateRef.compareAndSet(null, ClientRenderState)) + { + LOGGER.warn("Failed to start renderer due to concurrency"); + ClientRenderState.close(); + return false; + } + else + { + return true; + } } public boolean isRendering() @@ -113,27 +121,34 @@ public class ClientLevelModule public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) { - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState == null) - { - // either the renderer hasn't been started yet, or is being reloaded - return; - } ClientRenderState.renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler); + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState == null) + { + // either the renderer hasn't been started yet, or is being reloaded + return; + } + ClientRenderState.renderer.drawLODs(mcModelViewMatrix, mcProjectionMatrix, partialTicks, profiler); } public void stopRenderer() { - LOGGER.info("Stopping renderer for " + this); ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState == null) - { - LOGGER.warn("Tried to stop renderer for " + this + " when it was not started!"); return; - } + LOGGER.info("Stopping renderer for " + this); + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState == null) + { + LOGGER.warn("Tried to stop renderer for " + this + " when it was not started!"); + return; + } // stop the render state while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null)) // TODO why is there a while loop here? { - ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState == null) - { - return; + ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState == null) + { + return; + } } - } ClientRenderState.close(); + ClientRenderState.close(); } //===============// @@ -141,47 +156,54 @@ public class ClientLevelModule //===============// public void saveWrites(ChunkSizedFullDataAccessor data) { - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); DhLodPos pos = data.getLodPos().convertToDetailLevel(CompleteFullDataSource.SECTION_SIZE_OFFSET); if (ClientRenderState != null) - { - ClientRenderState.renderSourceFileHandler.writeChunkDataToFile(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data); - } - else - { - parent.getFileHandler().write(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data); - } + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + DhLodPos pos = data.getLodPos().convertToDetailLevel(CompleteFullDataSource.SECTION_SIZE_OFFSET); + if (ClientRenderState != null) + { + ClientRenderState.renderSourceFileHandler.writeChunkDataToFile(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data); + } + else + { + parent.getFileHandler().write(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data); + } } public CompletableFuture saveAsync() { - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState != null) - { - return ClientRenderState.renderSourceFileHandler.flushAndSaveAsync(); - } - else - { - return CompletableFuture.completedFuture(null); - } + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState != null) + { + return ClientRenderState.renderSourceFileHandler.flushAndSaveAsync(); + } + else + { + return CompletableFuture.completedFuture(null); + } } public void close() { // shutdown the renderer - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState != null) - { - // TODO does this have to be in a while loop, if so why? - while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null)) - { - ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState == null) - { - break; - } - } - + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState != null) { - ClientRenderState.close(); + // TODO does this have to be in a while loop, if so why? + while (!this.ClientRenderStateRef.compareAndSet(ClientRenderState, null)) + { + ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState == null) + { + break; + } + } + + if (ClientRenderState != null) + { + ClientRenderState.close(); + } } - } f3Message.close(); + + this.f3Message.close(); } @@ -199,30 +221,34 @@ public class ClientLevelModule /** Returns what should be displayed in Minecraft's F3 debug menu */ protected String[] f3Log() { - String dimName = parent.getClientLevelWrapper().getDimensionType().getDimensionName(); ClientRenderState renderState = this.ClientRenderStateRef.get(); if (renderState == null) - { - return new String[]{"level @ " + dimName + ": Inactive"}; - } - else - { - return new String[]{"level @ " + dimName + ": Active"}; - } + String dimName = parent.getClientLevelWrapper().getDimensionType().getDimensionName(); + ClientRenderState renderState = this.ClientRenderStateRef.get(); + if (renderState == null) + { + return new String[]{"level @ " + dimName + ": Inactive"}; + } + else + { + return new String[]{"level @ " + dimName + ": Active"}; + } } public void clearRenderCache() { - ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); if (ClientRenderState != null && ClientRenderState.quadtree != null) - { - ClientRenderState.quadtree.clearRenderDataCache(); - } + ClientRenderState ClientRenderState = this.ClientRenderStateRef.get(); + if (ClientRenderState != null && ClientRenderState.quadtree != null) + { + ClientRenderState.quadtree.clearRenderDataCache(); + } } public void reloadPos(DhSectionPos pos) { - ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); if (clientRenderState != null && clientRenderState.quadtree != null) - { - clientRenderState.quadtree.reloadPos(pos); - } + ClientRenderState clientRenderState = this.ClientRenderStateRef.get(); + if (clientRenderState != null && clientRenderState.quadtree != null) + { + clientRenderState.quadtree.reloadPos(pos); + } } public static class ClientRenderState @@ -236,15 +262,19 @@ public class ClientLevelModule public final LodRenderer renderer; public ClientRenderState( - IDhClientLevel dhClientLevel, IFullDataSourceProvider fullDataSourceProvider, AbstractSaveStructure saveStructure) + IDhClientLevel dhClientLevel, IFullDataSourceProvider fullDataSourceProvider, + AbstractSaveStructure saveStructure) { - this.levelWrapper = dhClientLevel.getLevelWrapper(); this.renderSourceFileHandler = new RenderSourceFileHandler(fullDataSourceProvider, dhClientLevel, saveStructure); + this.levelWrapper = dhClientLevel.getLevelWrapper(); + this.renderSourceFileHandler = new RenderSourceFileHandler(fullDataSourceProvider, dhClientLevel, saveStructure); this.quadtree = new LodQuadTree(dhClientLevel, Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH, // initial position is (0,0) just in case the player hasn't loaded in yet, the tree will be moved once the level starts ticking - 0, 0, this.renderSourceFileHandler); + 0, 0, + this.renderSourceFileHandler); - RenderBufferHandler renderBufferHandler = new RenderBufferHandler(this.quadtree); this.renderer = new LodRenderer(renderBufferHandler); + RenderBufferHandler renderBufferHandler = new RenderBufferHandler(this.quadtree); + this.renderer = new LodRenderer(renderBufferHandler); } @@ -253,7 +283,9 @@ public class ClientLevelModule { LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName()); - this.renderer.close(); this.quadtree.close(); this.renderSourceFileHandler.close(); + this.renderer.close(); + this.quadtree.close(); + this.renderSourceFileHandler.close(); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java index d9a954455..acc6ce14a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java @@ -32,7 +32,8 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS public final ClientLevelModule clientside; private final IServerLevelWrapper serverLevelWrapper; - public IClientLevelWrapper clientLevelWrapper; + + public DhClientServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper) { @@ -41,11 +42,13 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS LOGGER.warn("unable to create data folder."); } this.serverLevelWrapper = serverLevelWrapper; - serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure); - clientside = new ClientLevelModule(this); + this.serverside = new ServerLevelModule(this, serverLevelWrapper, saveStructure); + this.clientside = new ClientLevelModule(this); LOGGER.info("Started " + DhClientServerLevel.class.getSimpleName() + " for " + serverLevelWrapper + " with saves at " + saveStructure); } + + //==============// // tick methods // //==============// @@ -105,11 +108,7 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS clientside.startRenderer(); } - public void stopRenderer() - { - clientside.stopRenderer(); - clientLevelWrapper = null; - } + public void stopRenderer() { this.clientside.stopRenderer(); } //================// // level handling // diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java b/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java index dfe30a940..97b0c9232 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/WorldGenModule.java @@ -3,6 +3,7 @@ package com.seibel.distanthorizons.core.level; import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataFileHandler; import com.seibel.distanthorizons.core.generation.IWorldGenerationQueue; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.logging.f3.F3Screen; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; import org.apache.logging.log4j.Logger; @@ -16,7 +17,9 @@ public class WorldGenModule implements Closeable private final GeneratedFullDataFileHandler dataFileHandler; private final GeneratedFullDataFileHandler.IOnWorldGenCompleteListener onWorldGenCompleteListener; + private final AtomicReference worldGenStateRef = new AtomicReference<>(); + private final F3Screen.DynamicMessage worldGenF3Message; public static abstract class WorldGenState { @@ -48,6 +51,22 @@ public class WorldGenModule implements Closeable { this.dataFileHandler = dataFileHandler; this.onWorldGenCompleteListener = onWorldGenCompleteListener; + this.worldGenF3Message = new F3Screen.DynamicMessage(() -> + { + WorldGenState worldGenState = this.worldGenStateRef.get(); + if (worldGenState != null) + { + int waiting = worldGenState.worldGenerationQueue.getWaitingTaskCount(); + int inProgress = worldGenState.worldGenerationQueue.getInProgressTaskCount(); + + return "World Gen Tasks: "+waiting+", (in progress: "+inProgress+")"; + } + else + { + return "World Gen Disabled"; + } + }); + } public void startWorldGen(GeneratedFullDataFileHandler dataFileHandler, WorldGenState newWgs) @@ -121,5 +140,6 @@ public class WorldGenModule implements Closeable } } dataFileHandler.close(); + this.worldGenF3Message.close(); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java index add3f6330..b8ad72317 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java @@ -329,8 +329,6 @@ public class DebugRenderer public void render(Mat4f transform) { - if (!Config.Client.Advanced.Debugging.debugWireframeRendering.get()) return; - transform_this_frame = transform; Vec3d cam = MC_RENDER.getCameraExactPosition(); camf = new Vec3f((float) cam.x, (float) cam.y, (float) cam.z); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java index 9a9c46c5f..ce8721347 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java @@ -301,7 +301,15 @@ public class LodRenderer GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0); this.shaderProgram.unbind(); - DebugRenderer.INSTANCE.render(modelViewProjectionMatrix); + + if (Config.Client.Advanced.Debugging.debugWireframeRendering.get()) + { + profiler.popPush("Debug wireframes"); + // Note: this can be very slow if a lot of boxes are being rendered + DebugRenderer.INSTANCE.render(modelViewProjectionMatrix); + profiler.popPush("LOD cleanup"); + } + GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT); minecraftGlState.restore(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java index a732842f9..ee77c442c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/RateLimitedThreadPoolExecutor.java @@ -15,6 +15,8 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor /** How long it took this thread to run its last task */ private final ThreadLocal lastRunDurationNanoTimeRef = ThreadLocal.withInitial(() -> -1L); + private Runnable onTerminatedEventHandler = null; + //==============// @@ -63,4 +65,23 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor this.lastRunDurationNanoTimeRef.set(System.nanoTime() - this.runStartNanoTimeRef.get()); } + @Override + protected void terminated() + { + super.terminated(); + if (this.onTerminatedEventHandler != null) + { + this.onTerminatedEventHandler.run(); + } + } + + + + //==============// + // custom logic // + //==============// + + /** only one event handler can be present at a time */ + public void setOnTerminatedEventHandler(Runnable runnable) { this.onTerminatedEventHandler = runnable; } + } \ No newline at end of file diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java index 6dd3317c9..150983118 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java @@ -19,9 +19,9 @@ import java.util.concurrent.ExecutorService; public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWorld, IDhServerWorld { - private final HashMap levelObjMap; - private final HashSet dhLevels; - public final LocalSaveStructure saveStructure; + private final HashMap levelWrapperByDhLevel = new HashMap<>(); + private final HashSet dhLevels = new HashSet<>(); + public final LocalSaveStructure saveStructure = new LocalSaveStructure(); public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client Server World Ticker Thread", 2); public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop @@ -30,12 +30,13 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor + //=============// + // constructor // + //=============// + public DhClientServerWorld() { super(EWorldEnvironment.Client_Server); - this.saveStructure = new LocalSaveStructure(); - this.levelObjMap = new HashMap<>(); - this.dhLevels = new HashSet<>(); LOGGER.info("Started DhWorld of type " + this.environment); @@ -44,12 +45,16 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor + //=========// + // methods // + //=========// + @Override public DhClientServerLevel getOrLoadLevel(ILevelWrapper wrapper) { if (wrapper instanceof IServerLevelWrapper) { - return this.levelObjMap.computeIfAbsent(wrapper, (levelWrapper) -> + return this.levelWrapperByDhLevel.computeIfAbsent(wrapper, (levelWrapper) -> { File levelFile = this.saveStructure.getLevelFolder(levelWrapper); LodUtil.assertTrue(levelFile != null); @@ -60,7 +65,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor } else { - return this.levelObjMap.computeIfAbsent(wrapper, (levelWrapper) -> + return this.levelWrapperByDhLevel.computeIfAbsent(wrapper, (levelWrapper) -> { IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper; IServerLevelWrapper serverLevelWrapper = clientLevelWrapper.tryGetServerSideWrapper(); @@ -68,7 +73,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor LodUtil.assertTrue(clientLevelWrapper.getDimensionType().equals(serverLevelWrapper.getDimensionType()), "tryGetServerSideWrapper returned a level for a different dimension. ClientLevelWrapper dim: " + clientLevelWrapper.getDimensionType().getDimensionName() + " ServerLevelWrapper dim: " + serverLevelWrapper.getDimensionType().getDimensionName()); - DhClientServerLevel level = this.levelObjMap.get(serverLevelWrapper); + DhClientServerLevel level = this.levelWrapperByDhLevel.get(serverLevelWrapper); if (level == null) { return null; @@ -81,7 +86,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor } @Override - public DhClientServerLevel getLevel(ILevelWrapper wrapper) { return this.levelObjMap.get(wrapper); } + public DhClientServerLevel getLevel(ILevelWrapper wrapper) { return this.levelWrapperByDhLevel.get(wrapper); } @Override public Iterable getAllLoadedLevels() { return this.dhLevels; } @@ -89,21 +94,23 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor @Override public void unloadLevel(ILevelWrapper wrapper) { - if (this.levelObjMap.containsKey(wrapper)) + if (this.levelWrapperByDhLevel.containsKey(wrapper)) { if (wrapper instanceof IServerLevelWrapper) { - LOGGER.info("Unloading level " + this.levelObjMap.get(wrapper)); - DhClientServerLevel clientServerLevel = this.levelObjMap.remove(wrapper); - this.dhLevels.remove(clientServerLevel); + LOGGER.info("Unloading level " + this.levelWrapperByDhLevel.get(wrapper)); + wrapper.onUnload(); + + DhClientServerLevel clientServerLevel = this.levelWrapperByDhLevel.remove(wrapper); clientServerLevel.close(); + this.dhLevels.remove(clientServerLevel); } else { // If the level wrapper is a Client Level Wrapper, then that means the client side leaves the level, // but note that the server side still has the level loaded. So, we don't want to unload the level, // we just want to stop rendering it. - this.levelObjMap.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere. + this.levelWrapperByDhLevel.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere. } } } @@ -137,10 +144,18 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor for (DhClientServerLevel level : this.dhLevels) { LOGGER.info("Unloading level " + level.getServerLevelWrapper().getDimensionType().getDimensionName()); + + // level wrapper shouldn't be null, but just in case + IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper(); + if (serverLevelWrapper != null) + { + serverLevelWrapper.onUnload(); + } + level.close(); } - this.levelObjMap.clear(); + this.levelWrapperByDhLevel.clear(); this.eventLoop.close(); LOGGER.info("Closed DhWorld of type " + this.environment); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java index 23e1d2113..e0e343e02 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java @@ -28,8 +28,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld @CheckForNull private final ClientNetworkState networkState; - // TODO why does this executor have 2 threads? - public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client World Ticker Thread", 2); + public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("DH Client World Ticker Thread"); public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); @@ -111,6 +110,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld if (this.levels.containsKey(wrapper)) { LOGGER.info("Unloading level " + this.levels.get(wrapper)); + wrapper.onUnload(); this.levels.remove(wrapper).close(); } } @@ -145,6 +145,14 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld for (DhClientLevel dhClientLevel : this.levels.values()) { LOGGER.info("Unloading level " + dhClientLevel.getLevelWrapper().getDimensionType().getDimensionName()); + + // level wrapper shouldn't be null, but just in case + IClientLevelWrapper clientLevelWrapper = dhClientLevel.getClientLevelWrapper(); + if (clientLevelWrapper != null) + { + clientLevelWrapper.onUnload(); + } + dhClientLevel.close(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java index 32f664861..fdbca1b2b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java @@ -28,6 +28,10 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld + //==============// + // constructors // + //==============// + public DhServerWorld() { super(EWorldEnvironment.Server_Only); @@ -54,6 +58,12 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld }); } + + + //=========// + // methods // + //=========// + public void addPlayer(IServerPlayerWrapper serverPlayer) { this.remotePlayerConnectionHandler.registerJoinedPlayer(serverPlayer); @@ -111,6 +121,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld if (this.levels.containsKey(wrapper)) { LOGGER.info("Unloading level {} ", this.levels.get(wrapper)); + wrapper.onUnload(); this.levels.remove(wrapper).close(); } } @@ -146,6 +157,14 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld for (DhServerLevel level : this.levels.values()) { LOGGER.info("Unloading level " + level.getLevelWrapper().getDimensionType().getDimensionName()); + + // level wrapper shouldn't be null, but just in case + IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper(); + if (serverLevelWrapper != null) + { + serverLevelWrapper.onUnload(); + } + level.close(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java index 0bc41d07b..8c660fccd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/ILevelWrapper.java @@ -39,10 +39,6 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable @Override IDhApiDimensionTypeWrapper getDimensionType(); - int getBlockLight(int x, int y, int z); - - int getSkyLight(int x, int y, int z); - @Override boolean hasCeiling(); @@ -65,7 +61,7 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable @Deprecated IBiomeWrapper getBiome(DhBlockPos pos); - // TODO implement onUnload - // necessary so ChunkToLodBuilder can have its cache cleared after the level closes + /** Fired when the level is being unloaded. Doesn't unload the level. */ + void onUnload(); } diff --git a/core/src/main/resources/assets/distanthorizons/lang/en_us.json b/core/src/main/resources/assets/distanthorizons/lang/en_us.json index f14754785..5eed3ea08 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -257,7 +257,7 @@ "distanthorizons.config.client.advanced.graphics.advancedGraphics.earthCurveRatio": "Earth Curve Ratio §6(EXPERIMENTAL)§r", "distanthorizons.config.client.advanced.graphics.advancedGraphics.earthCurveRatio.@tooltip": - "A value of 1 is equivalent to the curvature of Earth in real life.", + "A value of 1 is equivalent to the curvature of Earth in real life. \nThe minimum accepted value is 50 and the maximum value is 5000. \nEverything between 1 and 49 will be rounded up to 50.", "distanthorizons.config.client.advanced.graphics.advancedGraphics.lodBias": "LOD Bias §6(Affects vanilla terrain)§r", "distanthorizons.config.client.advanced.graphics.advancedGraphics.lodBias.@tooltip": diff --git a/core/src/main/resources/shaders/curve.vert b/core/src/main/resources/shaders/curve.vert index 5db053d1e..03876a40c 100644 --- a/core/src/main/resources/shaders/curve.vert +++ b/core/src/main/resources/shaders/curve.vert @@ -8,6 +8,8 @@ out vec4 vertexColor; out vec3 vertexWorldPos; out float vertexYPos; +uniform bool whiteWorld; + uniform mat4 combinedMatrix; uniform vec3 modelOffset; uniform float worldYOffset; @@ -19,6 +21,8 @@ uniform float mircoOffset; uniform float earthRadius; /** + * TODO in the future this and standard.vert should be merged together to prevent inconsistencies between the two + * * Vertex Shader * * author: James Seibel @@ -51,43 +55,25 @@ void main() vertexWorldPos.x += mx; vertexWorldPos.y += my; vertexWorldPos.z += mz; - - #if 0 - // Old (disabled) vertex transformation logic - Leetom - - // Calculate the vertex pos due to curvature of the earth - // We use spherical coordinates to calculate the vertex position - //if (vertexWorldPos.x == 0.0 && vertexWorldPos.z == 0.0) - //{ - // // In the center. No curvature needed - //} - //else - //{ - float theta = atan(vertexWorldPos.z, vertexWorldPos.x); // in radians (-pi, pi) - float trueY = earthRadius + vertexWorldPos.y; // true Y position, or height - float phi = sqrt(vertexWorldPos.z * vertexWorldPos.z + vertexWorldPos.x * vertexWorldPos.x) / trueY; - // Convert spherical coordinates to cartesian coordinates - vertexWorldPos.x = trueY * sin(phi) * cos(theta); - vertexWorldPos.z = trueY * sin(phi) * sin(theta); - vertexWorldPos.y = trueY * cos(phi) - earthRadius; - //} - - #else - // new vertex transformation logic - stduhpf - - float localRadius = earthRadius + vertexYPos;// vertexWorldPos.y + cameraPosition.y - Center_Y; - - float phi = length(vertexWorldPos.xz) / localRadius; - - vertexWorldPos.y += (cos(phi) - 1.0) * localRadius; - vertexWorldPos.xz = vertexWorldPos.xz * sin(phi) / phi; - #endif - + + + // vertex transformation logic - stduhpf + float localRadius = earthRadius + vertexYPos; + float phi = length(vertexWorldPos.xz) / localRadius; + vertexWorldPos.y += (cos(phi) - 1.0) * localRadius; + vertexWorldPos.xz = vertexWorldPos.xz * sin(phi) / phi; + + uint lights = meta & 0xFFu; float light2 = (mod(float(lights), 16.0) + 0.5) / 16.0; float light = (float(lights / 16u) + 0.5) / 16.0; - vertexColor = color * vec4(texture(lightMap, vec2(light, light2)).xyz, 1.0); - + vertexColor = vec4(texture(lightMap, vec2(light, light2)).xyz, 1.0); + + if (!whiteWorld) + { + vertexColor *= color; + } + gl_Position = combinedMatrix * vec4(vertexWorldPos, 1.0); } diff --git a/core/src/main/resources/shaders/ssao/ao.frag b/core/src/main/resources/shaders/ssao/ao.frag index 33be6a37d..9538f54c4 100644 --- a/core/src/main/resources/shaders/ssao/ao.frag +++ b/core/src/main/resources/shaders/ssao/ao.frag @@ -46,16 +46,17 @@ void main() { vec3 samplePos = vec3(0.0) + (TBN * gKernel[i]); samplePos = viewPos + samplePos * gSampleRad; - + vec4 offset = gProj * vec4(samplePos + viewPos, 1.0); offset.xy /= offset.w; offset.xy = offset.xy * HALF_2 + HALF_2; - + float geometryDepth = calcViewPosition(offset.xy).z; - + float rangeCheck = smoothstep(0.0, 1.0, gSampleRad / abs(viewPos.z - geometryDepth)); - occlusion_factor += float(geometryDepth >= samplePos.z + 0.05) * rangeCheck; - + // the number added to the samplePos.z can be used to reduce noise in the SSAO application at the cost of reducing the overall affect + occlusion_factor += float(geometryDepth >= samplePos.z + 1.0) * rangeCheck; + } float visibility_factor = 1.0 - (occlusion_factor / MAX_KERNEL_SIZE); diff --git a/core/src/main/resources/shaders/standard.vert b/core/src/main/resources/shaders/standard.vert index c7e446cf8..316559b6e 100644 --- a/core/src/main/resources/shaders/standard.vert +++ b/core/src/main/resources/shaders/standard.vert @@ -20,6 +20,8 @@ uniform float mircoOffset; /** + * TODO in the future this and curve.vert should be merged together to prevent inconsistencies between the two + * * Vertex Shader * * author: James Seibel