diff --git a/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EDhApiGpuUploadMethod.java b/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EDhApiGpuUploadMethod.java index df4c4a767..8b6b737f8 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EDhApiGpuUploadMethod.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EDhApiGpuUploadMethod.java @@ -23,13 +23,12 @@ package com.seibel.distanthorizons.api.enums.config; * AUTO,
* BUFFER_STORAGE,
* SUB_DATA,
- * BUFFER_MAPPING,
* DATA
* * @author Leetom * @author James Seibel * @version 2024-4-6 - * @since API 2.0.0 + * @since API 3.0.0 */ public enum EDhApiGpuUploadMethod { @@ -49,7 +48,10 @@ public enum EDhApiGpuUploadMethod * May end up storing buffers in System memory.
* Fast rending if in GPU memory, slow if in system memory,
* but won't stutter when uploading. + * + * @deprecated not currently supported */ + @Deprecated BUFFER_MAPPING(true, false), /** Fast rendering but may stutter when uploading. */ diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/IDhApiConfig.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/IDhApiConfig.java index a0f81b499..fd7a5b8b4 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/IDhApiConfig.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/IDhApiConfig.java @@ -36,7 +36,6 @@ public interface IDhApiConfig IDhApiWorldGenerationConfig worldGenerator(); IDhApiMultiplayerConfig multiplayer(); IDhApiMultiThreadingConfig multiThreading(); - IDhApiGpuBuffersConfig gpuBuffers(); // note: DON'T add the Auto Updater to this API. We only want the user's to have the ability to control when things are downloaded to their machines. //IDhApiLoggingConfig logging(); // TODO implement IDhApiDebuggingConfig debugging(); diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGpuBuffersConfig.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGpuBuffersConfig.java deleted file mode 100644 index 4b0433f7a..000000000 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGpuBuffersConfig.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of the Distant Horizons mod - * licensed under the GNU LGPL v3 License. - * - * Copyright (C) 2020-2023 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 . - */ - -package com.seibel.distanthorizons.api.interfaces.config.client; - -import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; -import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup; -import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue; - -/** - * Distant Horizons' OpenGL buffer configuration. - * - * @author James Seibel - * @version 2023-6-14 - * @since API 1.0.0 - */ -public interface IDhApiGpuBuffersConfig extends IDhApiConfigGroup -{ - - /** Defines how geometry data is uploaded to the GPU. */ - IDhApiConfigValue gpuUploadMethod(); - - /** - * Defines how long we should wait after uploading one - * Megabyte of geometry data to the GPU before uploading - * the next Megabyte of data.
- * This can be set to a non-zero number to reduce stuttering caused by - * uploading buffers to the GPU. - */ - IDhApiConfigValue gpuUploadPerMegabyteInMilliseconds(); - -} diff --git a/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java index 70535e5e8..fd8a064c9 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/objects/data/DhApiChunk.java @@ -21,8 +21,10 @@ package com.seibel.distanthorizons.api.objects.data; import com.seibel.distanthorizons.api.interfaces.factories.IDhApiWrapperFactory; import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator; +import org.apache.logging.log4j.LogManager; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -66,7 +68,7 @@ public class DhApiChunk * @since API 3.0.0 */ public static DhApiChunk create(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos) - { return new DhApiChunk(chunkPosX, chunkPosZ, topYBlockPos, bottomYBlockPos, false); } + { return new DhApiChunk(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos, false); } /** * Only visible to internal DH methods @@ -114,27 +116,50 @@ public class DhApiChunk */ public void setDataPoints(int relX, int relZ, List dataPoints) throws IndexOutOfBoundsException, IllegalArgumentException { + //================// + // validate input // + //================// + + int internalArrayIndex = (relZ << 4) | relX; throwIfRelativePosOutOfBounds(relX, relZ); - // validate the incoming datapoints - if (dataPoints != null) + // ignore empty inputs + if (dataPoints == null) { - for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation + return; + } + + // check that each datapoint is valid + for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation + { + DhApiTerrainDataPoint dataPoint = dataPoints.get(i); + if (dataPoint == null) { - DhApiTerrainDataPoint dataPoint = dataPoints.get(i); - if (dataPoint == null) - { - throw new IllegalArgumentException("Null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR."); - } - - if (dataPoint.detailLevel != 0) - { - throw new IllegalArgumentException("DhApiTerrainDataPoints has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0]."); - } + throw new IllegalArgumentException("Null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR."); + } + + if (dataPoint.detailLevel != 0) + { + throw new IllegalArgumentException("DhApiTerrainDataPoints has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0]."); } } - this.dataPoints.set((relZ << 4) | relX, dataPoints); + + + //================// + // set datapoints // + //================// + + // DH expects datapoints to be in a top-down order + DhApiTerrainDataPoint first = dataPoints.get(0); + DhApiTerrainDataPoint last = dataPoints.get(dataPoints.size() - 1); + if (first.bottomYBlockPos < last.bottomYBlockPos) + { + // flip the array if it's in bottom-up order + Collections.reverse(dataPoints); + } + + this.dataPoints.set(internalArrayIndex, dataPoints); } diff --git a/api/src/main/java/com/seibel/distanthorizons/coreapi/util/StringUtil.java b/api/src/main/java/com/seibel/distanthorizons/coreapi/util/StringUtil.java index f57efbe15..44c94b197 100644 --- a/api/src/main/java/com/seibel/distanthorizons/coreapi/util/StringUtil.java +++ b/api/src/main/java/com/seibel/distanthorizons/coreapi/util/StringUtil.java @@ -24,12 +24,12 @@ import java.util.Arrays; /** * Miscellaneous string helper functions. - * - * @author James Seibel - * @version 2022-7-19 */ public class StringUtil { + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + + /** * Returns the n-th index of the given string.

* @@ -67,8 +67,6 @@ public class StringUtil return stringBuilder.toString(); } - - private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); /** * Converts the given byte array into a hex string representation.
* source: https://stackoverflow.com/a/9855338 @@ -85,4 +83,20 @@ public class StringUtil return new String(hexChars); } + /** + * Returns a shortened version of the given string that is no longer than maxLength.
+ * If null returns the empty string. + */ + public static String shortenString(String str, int maxLength) + { + if (str == null) + { + return ""; + } + else + { + return str.substring(0, Math.min(str.length(), maxLength)); + } + } + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/DhApiConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/DhApiConfig.java index 5197f7b46..1abbf6d32 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/DhApiConfig.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/DhApiConfig.java @@ -42,8 +42,6 @@ public class DhApiConfig implements IDhApiConfig @Override public IDhApiMultiThreadingConfig multiThreading() { return DhApiMultiThreadingConfig.INSTANCE; } @Override - public IDhApiGpuBuffersConfig gpuBuffers() { return DhApiGpuBuffersConfig.INSTANCE; } - @Override public IDhApiDebuggingConfig debugging() { return DhApiDebuggingConfig.INSTANCE; } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGpuBuffersConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGpuBuffersConfig.java deleted file mode 100644 index 22f4852b0..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGpuBuffersConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This file is part of the Distant Horizons mod - * licensed under the GNU LGPL v3 License. - * - * Copyright (C) 2020-2023 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 . - */ - -package com.seibel.distanthorizons.core.api.external.methods.config.client; - -import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue; -import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiGpuBuffersConfig; -import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue; -import com.seibel.distanthorizons.core.config.Config; -import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; - -public class DhApiGpuBuffersConfig implements IDhApiGpuBuffersConfig -{ - public static DhApiGpuBuffersConfig INSTANCE = new DhApiGpuBuffersConfig(); - - private DhApiGpuBuffersConfig() { } - - - - public IDhApiConfigValue gpuUploadMethod() - { return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadMethod); } - - public IDhApiConfigValue gpuUploadPerMegabyteInMilliseconds() - { return new DhApiConfigValue<>(Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds); } - -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java index e6cec2d17..2abac5a12 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java @@ -386,7 +386,7 @@ public class ClientApi { // logging // - this.sendChatMessagesNow(); + this.sendQueuedChatMessages(); IProfilerWrapper profiler = MC.getProfiler(); profiler.pop(); // get out of "terrain" @@ -554,7 +554,7 @@ public class ClientApi } } - private void sendChatMessagesNow() + private void sendQueuedChatMessages() { // dev build if (ModInfo.IS_DEV_BUILD && !this.configOverrideReminderPrinted && MC.playerExists()) @@ -562,10 +562,12 @@ public class ClientApi this.configOverrideReminderPrinted = true; // remind the user that this is a development build - MC.sendChatMessage("\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r"); - MC.sendChatMessage("Issues may occur with this version."); - MC.sendChatMessage("Here be dragons!"); - MC.sendChatMessage(""); + String message = + // green text + "\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r\n" + + "Issues may occur with this version.\n" + + "Here be dragons!\n"; + MC.sendChatMessage(message); } // memory @@ -580,11 +582,13 @@ public class ClientApi long maxMemoryInBytes = Runtime.getRuntime().maxMemory(); if (maxMemoryInBytes < minimumRecommendedMemoryInBytes) { - MC.sendChatMessage("\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r"); - MC.sendChatMessage("Stuttering or low FPS may occur."); - MC.sendChatMessage("Please increase Minecraft's available memory to 4 gigabytes."); - MC.sendChatMessage("This warning can be disabled in DH's config under Advanced -> Logging."); - MC.sendChatMessage(""); + String message = + // orange text + "\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r \n" + + "Stuttering or low FPS may occur. \n" + + "Please increase Minecraft's available memory to 4 gigabytes. \n" + + "This warning can be disabled in DH's config under Advanced -> Logging. \n"; + MC.sendChatMessage(message); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index 26b1afa9b..9fb3d53c7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.level.IDhLevel; +import com.seibel.distanthorizons.core.level.IDhServerLevel; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.f3.F3Screen; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; @@ -31,6 +32,7 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo; +import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.objects.Pair; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; @@ -46,6 +48,7 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicInteger; /** Contains code and variables used by both {@link ClientApi} and {@link ServerApi} */ public class SharedApi @@ -333,36 +336,50 @@ public class SharedApi } - // Save or populate the chunk wrapper's lighting - // this is done so we don't have to worry about MC unloading the lighting data for this chunk - boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get(); - if (!onlyUseDhLighting && chunkWrapper.isLightCorrect()) + // chunk light baking is disabled since profiling revealed it used + // roughly the same amount of time as generating the lighting ourselves and + // was much more likely to have issues with corrupt (all black or all bright) chunks + boolean tryUsingMcLightingEngine = false; + if (tryUsingMcLightingEngine) { - try + // Save or populate the chunk wrapper's lighting + // this is done so we don't have to worry about MC unloading the lighting data for this chunk + boolean chunkLightPopulated = false; + boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get(); + if (!onlyUseDhLighting && chunkWrapper.isLightCorrect()) { // If MC's lighting engine isn't thread safe this may cause the server thread to lag - chunkWrapper.bakeDhLightingUsingMcLightingEngine(); + chunkLightPopulated = chunkWrapper.bakeDhLightingUsingMcLightingEngine(dhLevel.getLevelWrapper()); + if (!chunkLightPopulated) + { + // clear any existing data to prevent partial or corrupt lighting + // when re-generating it + chunkWrapper.clearDhBlockLighting(); + chunkWrapper.clearDhSkyLighting(); + } } - catch (IllegalStateException e) + + // something went wrong during the baking process so we have to generate the lighting ourselves + if (!chunkLightPopulated) { - LOGGER.warn("Chunk light baking error: " + e.getMessage(), e); + DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); } } else { - // generate the chunk's lighting, using neighboring chunks if present - DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? 15 : 0); + DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); } + + // get this chunk's active beacons List beaconBeamList = chunkWrapper.getAllActiveBeacons(nearbyChunkList); dhLevel.setBeaconBeamsForChunk(chunkWrapper.getChunkPos(), beaconBeamList); - dhLevel.updateChunkAsync(chunkWrapper); - dhLevel.setChunkHash(chunkWrapper.getChunkPos(), newChunkHash); + dhLevel.updateChunkAsync(chunkWrapper, newChunkHash); } catch (Exception e) { 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 3b8206b73..4e786f94c 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 @@ -54,7 +54,6 @@ import java.util.List; * Otherwise, you will have issues where only some of the config entries will exist when your listener is created. * * @author coolGi - * @version 2023-7-16 */ public class Config @@ -126,7 +125,6 @@ public class Config public static ConfigCategory multiplayer = new ConfigCategory.Builder().set(Multiplayer.class).build(); public static ConfigCategory lodBuilding = new ConfigCategory.Builder().set(LodBuilding.class).build(); public static ConfigCategory multiThreading = new ConfigCategory.Builder().set(MultiThreading.class).build(); - public static ConfigCategory buffers = new ConfigCategory.Builder().set(GpuBuffers.class).build(); public static ConfigCategory autoUpdater = new ConfigCategory.Builder().set(AutoUpdater.class).build(); public static ConfigCategory logging = new ConfigCategory.Builder().set(Logging.class).build(); @@ -187,6 +185,7 @@ public class Config + "Lowest Quality: " + EDhApiVerticalQuality.HEIGHT_MAP + "\n" + "Highest Quality: " + EDhApiVerticalQuality.EXTREME) .setPerformance(EConfigEntryPerformance.VERY_HIGH) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry horizontalQuality = new ConfigEntry.Builder() @@ -208,6 +207,7 @@ public class Config + EDhApiTransparency.DISABLED + ": LODs will be opaque. \n" + "") .setPerformance(EConfigEntryPerformance.MEDIUM) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry blocksToIgnore = new ConfigEntry.Builder() @@ -219,6 +219,7 @@ public class Config + EDhApiBlocksToAvoid.NON_COLLIDING + ": Only represent solid blocks in the LODs (tall grass, torches, etc. won't count for a LOD's height) \n" + "") .setPerformance(EConfigEntryPerformance.NONE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry tintWithAvoidedBlocks = new ConfigEntry.Builder() @@ -230,6 +231,7 @@ public class Config + "False: skipped blocks will not change color of surface below them. " + "") .setPerformance(EConfigEntryPerformance.NONE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); // TODO fixme @@ -595,6 +597,7 @@ public class Config + "0 = black \n" + "1 = normal \n" + "2 = near white") + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry saturationMultiplier = new ConfigEntry.Builder() // TODO: Make this a float (the ClassicConfigGUI doesnt support floats) @@ -605,6 +608,7 @@ public class Config + "0 = black and white \n" + "1 = normal \n" + "2 = very saturated") + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry enableCaveCulling = new ConfigEntry.Builder() @@ -619,14 +623,15 @@ public class Config + "Additional Info: Currently this cull all faces \n" + " with skylight value of 0 in dimensions that \n" + " does not have a ceiling.") + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); - @Deprecated public static ConfigEntry caveCullingHeight = new ConfigEntry.Builder() - .setMinDefaultMax(-4096, 40, 4096) - .setAppearance(EConfigEntryAppearance.ONLY_IN_API) + .setMinDefaultMax(-4096, 60, 4096) .comment("" - + "At what Y value should cave culling start?") + + "At what Y value should cave culling start? \n" + + "Lower this value if you get walls for areas with 0 light.") + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry earthCurveRatio = new ConfigEntry.Builder() @@ -664,6 +669,7 @@ public class Config + EDhApiLodShading.DISABLED + ": All LOD sides will be rendered with the same brightness. \n" + "") .setPerformance(EConfigEntryPerformance.NONE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry disableFrustumCulling = new ConfigEntry.Builder() @@ -698,6 +704,7 @@ public class Config + EDhApiGrassSideRendering.AS_DIRT + ": sides render entirely as dirt. \n" + "") .setPerformance(EConfigEntryPerformance.NONE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); } @@ -782,8 +789,10 @@ public class Config + "") .build(); + @Deprecated public static ConfigEntry onlyUseDhLightingEngine = new ConfigEntry.Builder() .set(false) + .setAppearance(EConfigEntryAppearance.ONLY_IN_API) .comment("" + "If false LODs will be lit by Minecraft's lighting engine when possible \n" + "and fall back to the DH lighting engine only when necessary. \n" @@ -1195,53 +1204,6 @@ public class Config .build(); } - public static class GpuBuffers - { - public static ConfigEntry gpuUploadMethod = new ConfigEntry.Builder() - .set(EDhApiGpuUploadMethod.AUTO) - .comment("" - + "What method should be used to upload geometry to the GPU? \n" - + "\n" - + EDhApiGpuUploadMethod.AUTO + ": Picks the best option based on the GPU you have. \n" - + "\n" - + EDhApiGpuUploadMethod.BUFFER_STORAGE + ": Default if OpenGL 4.5 is supported. \n" - + " Fast rendering, no stuttering. \n" - + "\n" - + EDhApiGpuUploadMethod.SUB_DATA + ": Backup option for NVIDIA. \n" - + " Fast rendering but may stutter when uploading. \n" - + "\n" - + EDhApiGpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. \n" - + " Generally the best option for integrated GPUs. \n" - + " Default option for AMD/Intel if OpenGL 4.5 isn't supported. \n" - + " May end up storing buffers in System memory. \n" - + " Fast rendering if in GPU memory, slow if in system memory, \n" - + " but won't stutter when uploading. \n" - + "\n" - + EDhApiGpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n" - + " Backup option for AMD/Intel. \n" - + " Fast rendering but may stutter when uploading. \n" - + "\n" - + "If you don't see any difference when changing these settings, \n" - + "or the world looks corrupted: restart your game." - + "") - .build(); - - public static ConfigEntry gpuUploadPerMegabyteInMilliseconds = new ConfigEntry.Builder() - .setMinDefaultMax(0, 0, 50) - .comment("" - + "How long should a buffer wait per Megabyte of data uploaded? \n" - + "Helpful resource for frame times: https://fpstoms.com \n" - + "\n" - + "Longer times may reduce stuttering but will make LODs \n" - + "transition and load slower. Change this to [0] for no timeout. \n" - + "\n" - + "NOTE:\n" - + "Before changing this config, try changing the \"GPU Upload method\" first. \n" - + "") - .build(); - - } - public static class AutoUpdater { public static ConfigEntry enableAutoUpdater = new ConfigEntry.Builder() @@ -1274,7 +1236,7 @@ public class Config // TODO default to error chat and info file public static ConfigEntry logWorldGenEvent = new ConfigEntry.Builder() .setServersideShortName("logWorldGenEvent") - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about the world generation process. \n" + "This can be useful for debugging.") @@ -1282,7 +1244,7 @@ public class Config public static ConfigEntry logWorldGenPerformance = new ConfigEntry.Builder() .setServersideShortName("logWorldGenPerformance") - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log performance about the world generation process. \n" + "This can be useful for debugging.") @@ -1290,7 +1252,7 @@ public class Config public static ConfigEntry logWorldGenLoadEvent = new ConfigEntry.Builder() .setServersideShortName("logWorldGenPerformance") - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about the world generation process. \n" + "This can be useful for debugging.") @@ -1298,21 +1260,21 @@ public class Config public static ConfigEntry logLodBuilderEvent = new ConfigEntry.Builder() .setServersideShortName("logLodBuilderEvent") - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about the LOD generation process. \n" + "This can be useful for debugging.") .build(); public static ConfigEntry logRendererBufferEvent = new ConfigEntry.Builder() - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about the renderer buffer process. \n" + "This can be useful for debugging.") .build(); public static ConfigEntry logRendererGLEvent = new ConfigEntry.Builder() - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about the renderer OpenGL process. \n" + "This can be useful for debugging.") @@ -1320,7 +1282,7 @@ public class Config public static ConfigEntry logFileReadWriteEvent = new ConfigEntry.Builder() .setServersideShortName("logFileReadWriteEvent") - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about file read/write operations. \n" + "This can be useful for debugging.") @@ -1328,7 +1290,7 @@ public class Config public static ConfigEntry logFileSubDimEvent = new ConfigEntry.Builder() .setServersideShortName("logFileSubDimEvent") - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about file sub-dimension operations. \n" + "This can be useful for debugging.") @@ -1336,7 +1298,7 @@ public class Config public static ConfigEntry logNetworkEvent = new ConfigEntry.Builder() .setServersideShortName("logNetworkEvent") - .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT) + .set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE) .comment("" + "If enabled, the mod will log information about network operations. \n" + "This can be useful for debugging.") @@ -1357,6 +1319,13 @@ public class Config + "giving some basic information about how DH will function.") .build(); + public static ConfigEntry showModCompatibilityWarningsOnStartup = new ConfigEntry.Builder() + .set(true) + .comment("" + + "If enabled, a chat message will be displayed when a potentially problematic \n" + + "mod is installed alongside DH.") + .build(); + } public static class Debugging @@ -1443,38 +1412,38 @@ public class Config public static ConfigEntry columnBuilderDebugEnable = new ConfigEntry.Builder() .set(false) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry columnBuilderDebugDetailLevel = new ConfigEntry.Builder() .set((int) DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry columnBuilderDebugXPos = new ConfigEntry.Builder() .set(0) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry columnBuilderDebugZPos = new ConfigEntry.Builder() .set(0) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry columnBuilderDebugXRow = new ConfigEntry.Builder() .set(-1) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry columnBuilderDebugZRow = new ConfigEntry.Builder() .set(-1) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); public static ConfigEntry columnBuilderDebugColumnIndex = new ConfigEntry.Builder() .set(-1) .setAppearance(EConfigEntryAppearance.ONLY_IN_GUI) - .addListener(DebugColumnConfigEventHandler.INSTANCE) + .addListener(ReloadLodsConfigEventHandler.INSTANCE) .build(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/DebugColumnConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/ReloadLodsConfigEventHandler.java similarity index 88% rename from core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/DebugColumnConfigEventHandler.java rename to core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/ReloadLodsConfigEventHandler.java index 93f55820d..9248280e5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/DebugColumnConfigEventHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/ReloadLodsConfigEventHandler.java @@ -23,9 +23,9 @@ import com.seibel.distanthorizons.api.DhApi; import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy; import com.seibel.distanthorizons.core.config.listeners.IConfigListener; -public class DebugColumnConfigEventHandler implements IConfigListener +public class ReloadLodsConfigEventHandler implements IConfigListener { - public static DebugColumnConfigEventHandler INSTANCE = new DebugColumnConfigEventHandler(); + public static ReloadLodsConfigEventHandler INSTANCE = new ReloadLodsConfigEventHandler(); @Override public void onConfigValueSet() diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java index 8aba01275..c88cd8e9c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/AbstractPresetConfigEventHandler.java @@ -170,7 +170,7 @@ public abstract class AbstractPresetConfigEventHandler= caveCullingMaxY + // check if this face is on a border + && + ( + (x == 0 && direction == EDhDirection.WEST) + || (z == 0 && direction == EDhDirection.NORTH) + // TODO why does 256 represent a border? aren't LODs only 64 datapoints wide? + || (x == 256 && direction == EDhDirection.EAST) + || (z == 256 && direction == EDhDirection.SOUTH) + ); + + byte newSkyLightAtPos = adjacentCoversThis ? adjSkyLight : SKYLIGHT_COVERED; + skyLightAtInputPos[i] = (byte) Math.min(newSkyLightAtPos, skyLightAtPos); } } else diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java index cea580bc5..833e0cf8b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBuffer.java @@ -33,9 +33,10 @@ import com.seibel.distanthorizons.core.util.objects.StatsMap; import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import org.apache.logging.log4j.Logger; +import org.lwjgl.system.MemoryUtil; import java.nio.ByteBuffer; -import java.util.Iterator; +import java.util.ArrayList; import java.util.concurrent.*; /** @@ -100,7 +101,7 @@ public class ColumnRenderBuffer implements AutoCloseable { try { - this.uploadBuffersUsingUploadMethod(builder, gpuUploadMethod); + this.uploadBuffers(builder, gpuUploadMethod); uploadFuture.complete(null); } catch (InterruptedException e) @@ -126,72 +127,46 @@ public class ColumnRenderBuffer implements AutoCloseable //LOGGER.warn("Error uploading builder ["+builder+"] synchronously. Error: "+e.getMessage(), e); } } - private void uploadBuffersUsingUploadMethod(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException + private void uploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException { - if (gpuUploadMethod.useEarlyMapping) - { - this.uploadBuffersMapped(builder, gpuUploadMethod); - } - else - { - this.uploadBuffersDirect(builder, gpuUploadMethod); - } + // uploading mapped buffers used to be done here, + // however due to a memory leak and complication with the previous code, + // now we only allow direct uploading. + // (There's also insufficient data to state whether mapped buffers are necessary + // for DH to upload without stuttering the main thread) + + this.vbos = makeAndUploadBuffers(builder, method, this.vbos, builder.makeOpaqueVertexBuffers()); + this.vbosTransparent = makeAndUploadBuffers(builder, method, this.vbosTransparent, builder.makeTransparentVertexBuffers()); this.buffersUploaded = true; } - - - - private void uploadBuffersMapped(LodQuadBuilder builder, EDhApiGpuUploadMethod method) + /** This resizes and returns the vbo array if necessary based on the amount of data needed for this area. */ + private static GLVertexBuffer[] makeAndUploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method, GLVertexBuffer[] vbos, ArrayList buffers) throws InterruptedException { - // opaque vbos // - - this.vbos = resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount()); - for (int i = 0; i < this.vbos.length; i++) + try { - if (this.vbos[i] == null) + vbos = resizeBuffer(vbos, buffers.size()); + uploadBuffersDirect(vbos, buffers, method); + } + finally + { + // all the buffers must be manually freed to prevent memory leaks + if (buffers != null) { - this.vbos[i] = new GLVertexBuffer(method.useBufferStorage); + for (ByteBuffer buffer : buffers) + { + MemoryUtil.memFree(buffer); + } } } - LodQuadBuilder.BufferFiller func = builder.makeOpaqueBufferFiller(method); - for (GLVertexBuffer vbo : this.vbos) - { - func.fill(vbo); - } - - // transparent vbos // - - this.vbosTransparent = resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount()); - for (int i = 0; i < this.vbosTransparent.length; i++) - { - if (this.vbosTransparent[i] == null) - { - this.vbosTransparent[i] = new GLVertexBuffer(method.useBufferStorage); - } - } - LodQuadBuilder.BufferFiller transparentFillerFunc = builder.makeTransparentBufferFiller(method); - for (GLVertexBuffer vbo : this.vbosTransparent) - { - transparentFillerFunc.fill(vbo); - } + // return the array in case it was resized + return vbos; } - - private void uploadBuffersDirect(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException + private static void uploadBuffersDirect(GLVertexBuffer[] vbos, ArrayList byteBuffers, EDhApiGpuUploadMethod method) throws InterruptedException { - this.vbos = resizeBuffer(this.vbos, builder.getCurrentNeededOpaqueVertexBufferCount()); - uploadBuffersDirect(this.vbos, builder.makeOpaqueVertexBuffers(), method); - - this.vbosTransparent = resizeBuffer(this.vbosTransparent, builder.getCurrentNeededTransparentVertexBufferCount()); - uploadBuffersDirect(this.vbosTransparent, builder.makeTransparentVertexBuffers(), method); - } - private static void uploadBuffersDirect(GLVertexBuffer[] vbos, Iterator iter, EDhApiGpuUploadMethod method) throws InterruptedException - { - long remainingMS = 0; - long MBPerMS = Config.Client.Advanced.GpuBuffers.gpuUploadPerMegabyteInMilliseconds.get(); int vboIndex = 0; - while (iter.hasNext()) + for (int i = 0; i < byteBuffers.size(); i++) { if (vboIndex >= vbos.length) { @@ -207,13 +182,13 @@ public class ColumnRenderBuffer implements AutoCloseable GLVertexBuffer vbo = vbos[vboIndex]; - ByteBuffer bb = iter.next(); - int size = bb.limit() - bb.position(); + ByteBuffer buffer = byteBuffers.get(i); + int size = buffer.limit() - buffer.position(); try { vbo.bind(); - vbo.uploadBuffer(bb, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER); + vbo.uploadBuffer(buffer, size / LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER); } catch (Exception e) { @@ -222,24 +197,6 @@ public class ColumnRenderBuffer implements AutoCloseable LOGGER.error("Failed to upload buffer: ", e); } - - if (MBPerMS > 0) - { - // upload buffers over an extended period of time - // to hopefully prevent stuttering. - remainingMS += size * MBPerMS; - if (remainingMS >= TimeUnit.NANOSECONDS.convert(1000 / 60, TimeUnit.MILLISECONDS)) - { - if (remainingMS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS) - { - remainingMS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS; - } - - Thread.sleep(remainingMS / 1000000, (int) (remainingMS % 1000000)); - remainingMS = 0; - } - } - vboIndex++; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java index def8f252c..78fabc59c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java @@ -61,22 +61,21 @@ public class ColumnRenderBufferBuilder // vbo building // //==============// - /** @link adjData should be null for adjacent sections that cross detail level boundaries */ - public static CompletableFuture buildAndUploadBuffersAsync( + public static CompletableFuture buildBuffersAsync( IDhClientLevel clientLevel, - ColumnRenderSource renderSource, ColumnRenderSource[] adjData) + ColumnRenderSource renderSource, ColumnRenderSource[] adjData, boolean[] isSameDetailLevel + ) { ThreadPoolExecutor bufferBuilderExecutor = ThreadPoolUtil.getBufferBuilderExecutor(); - ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor(); - if ((bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated()) || - (bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated())) + if (bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated()) { // one or more of the thread pools has been shut down - CompletableFuture future = new CompletableFuture<>(); + CompletableFuture future = new CompletableFuture<>(); future.cancel(true); return future; } + try { return CompletableFuture.supplyAsync(() -> @@ -85,7 +84,7 @@ public class ColumnRenderBufferBuilder { boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled; LodQuadBuilder builder = new LodQuadBuilder(enableTransparency, clientLevel.getClientLevelWrapper()); - makeLodRenderData(builder, renderSource, adjData); + makeLodRenderData(builder, renderSource, clientLevel, adjData, isSameDetailLevel); return builder; } catch (UncheckedInterruptedException e) @@ -97,8 +96,39 @@ public class ColumnRenderBufferBuilder LOGGER.error("LodNodeBufferBuilder was unable to build quads for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: ["+ e3.getMessage()+"].", e3); throw e3; } - }, bufferBuilderExecutor) - .thenApplyAsync((quadBuilder) -> + }, bufferBuilderExecutor); + } + catch (RejectedExecutionException ignore) + { + // the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back + + CompletableFuture future = new CompletableFuture<>(); + future.cancel(true); + return future; + } + } + + /** @link adjData should be null for adjacent sections that cross detail level boundaries */ + public static CompletableFuture uploadBuffersAsync( + IDhClientLevel clientLevel, + ColumnRenderSource renderSource, + LodQuadBuilder quadBuilder + ) + { + // TODO put into a single future/thread so it can be easily canceled + ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor(); + if (bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated()) + { + // one or more of the thread pools has been shut down + CompletableFuture future = new CompletableFuture<>(); + future.cancel(true); + return future; + } + + + try + { + return CompletableFuture.supplyAsync(() -> { try { @@ -128,21 +158,23 @@ public class ColumnRenderBufferBuilder } catch (Throwable e3) { - LOGGER.error("LodNodeBufferBuilder was unable to upload buffer: " + e3.getMessage(), e3); + LOGGER.error("LodNodeBufferBuilder was unable to upload buffer for pos ["+DhSectionPos.toString(renderSource.pos)+"], error: [" + e3.getMessage() + "].", e3); throw e3; } }, bufferUploaderExecutor); } catch (RejectedExecutionException ignore) { - // the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back + // shouldn't happen, but just in case CompletableFuture future = new CompletableFuture<>(); future.cancel(true); return future; } } - private static void makeLodRenderData(LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, ColumnRenderSource[] adjRegions) + private static void makeLodRenderData( + LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel, + ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel) { //=============// // debug check // @@ -332,8 +364,9 @@ public class ColumnRenderBufferBuilder long bottomDataPoint = (i + 1) < columnRenderData.size() ? columnRenderData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA; addLodToBuffer( + clientLevel, data, topDataPoint, bottomDataPoint, - adjColumnViews, + adjColumnViews, isSameDetailLevel, thisDetailLevel, relX, relZ, quadBuilder, debugSourceFlag); } @@ -344,8 +377,9 @@ public class ColumnRenderBufferBuilder quadBuilder.finalizeData(); } private static void addLodToBuffer( + IDhClientLevel clientLevel, long data, long topData, long bottomData, - ColumnArrayView[] adjColumnViews, + ColumnArrayView[] adjColumnViews, boolean[] isSameDetailLevel, byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ, LodQuadBuilder quadBuilder, ColumnRenderSource.DebugSourceFlag debugSource) { @@ -475,14 +509,14 @@ public class ColumnRenderBufferBuilder } ColumnBox.addBoxQuadsToBuilder( - quadBuilder, // buffer - width, ySize, width, // setWidth - x, yMin, z, // setOffset - color, // setColor - blockMaterialId, // irisBlockMaterialId - RenderDataPointUtil.getLightSky(data), // setSkyLights - fullBright ? 15 : RenderDataPointUtil.getLightBlock(data), // setBlockLights - topData, bottomData, adjColumnViews); // setAdjData + quadBuilder, clientLevel, + width, ySize, width, + x, yMin, z, + color, + blockMaterialId, + RenderDataPointUtil.getLightSky(data), + fullBright ? 15 : RenderDataPointUtil.getLightBlock(data), + topData, bottomData, adjColumnViews, isSameDetailLevel); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java index b9db5c836..89cbcfec3 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java @@ -38,6 +38,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.coreapi.util.MathUtil; import org.apache.logging.log4j.Logger; +import org.lwjgl.system.MemoryUtil; /** * Used to create the quads before they are converted to render-able buffers.

@@ -205,10 +206,124 @@ public class LodQuadBuilder + //=================// + // data finalizing // + //=================// + + /** runs any final data cleanup, merging, etc. */ + public void finalizeData() { this.mergeQuads(); } + + /** Uses Greedy meshing to merge this builder's Quads. */ + public void mergeQuads() + { + long mergeCount = 0; // can be used for debugging + long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount(); + if (preQuadsCount <= 1) + { + return; + } + + for (int directionIndex = 0; directionIndex < 6; directionIndex++) + { + mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest); + if (this.doTransparency) + { + mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest); + } + + + // only run the second merge if the face is the top or bottom + if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal()) + { + mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown); + if (this.doTransparency) + { + mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown); + } + } + } + + //long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount(); + //LOGGER.trace("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads"); + } + + /** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */ + private static long mergeQuadsInternal(ArrayList[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection) + { + if (list[directionIndex].size() <= 1) + return 0; + + list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection)); + + long mergeCount = 0; + ListIterator iter = list[directionIndex].listIterator(); + BufferQuad currentQuad = iter.next(); + while (iter.hasNext()) + { + BufferQuad nextQuad = iter.next(); + + if (currentQuad.tryMerge(nextQuad, mergeDirection)) + { + // merge successful, attempt to merge the next quad + mergeCount++; + iter.set(null); + } + else + { + // merge fail, move on to the next quad + currentQuad = nextQuad; + } + } + list[directionIndex].removeIf(Objects::isNull); + return mergeCount; + } + + + //==============// - // add vertices // + // buffer setup // //==============// + public ArrayList makeOpaqueVertexBuffers() { return this.makeVertexBuffers(this.opaqueQuads); } + public ArrayList makeTransparentVertexBuffers() { return this.makeVertexBuffers(this.transparentQuads); } + private ArrayList makeVertexBuffers(ArrayList[] quadList) + { + ArrayList byteBufferList = new ArrayList<>(3); + + ByteBuffer buffer = null; + for (int directionIndex = 0; directionIndex < 6; directionIndex++) + { + // ignore empty directions + if (quadList[directionIndex].isEmpty()) + { + continue; + } + + // put all the quads in this direction into the buffer + for (int quadIndex = 0; quadIndex < quadList[directionIndex].size(); quadIndex++) + { + // if this is the first iteration or the buffer is full, + // create a new buffer + if (buffer == null || !buffer.hasRemaining()) + { + buffer = MemoryUtil.memAlloc(ColumnRenderBuffer.FULL_SIZED_BUFFER); + byteBufferList.add(buffer); + } + + this.putQuad(buffer, quadList[directionIndex].get(quadIndex)); + } + } + + // rewind all the buffers so they can be read from + for (int i = 0; i < byteBufferList.size(); i++) + { + buffer = byteBufferList.get(i); + buffer.limit(buffer.position()); + buffer.rewind(); + } + + return byteBufferList; + } private void putQuad(ByteBuffer bb, BufferQuad quad) { int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()]; @@ -266,10 +381,10 @@ public class LodQuadBuilder if (quad.direction.getAxis().isHorizontal() || quad.direction == EDhDirection.DOWN) { if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT - // if we want the color to fade, only apply the dirt color to the bottom vertices - || (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0) - // always render the bottom as dirt - || quad.direction == EDhDirection.DOWN) + // if we want the color to fade, only apply the dirt color to the bottom vertices + || (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0) + // always render the bottom as dirt + || quad.direction == EDhDirection.DOWN) { // for horizontal and bottom faces of grass blocks, use the dirt color to // prevent green cliff walls @@ -277,7 +392,7 @@ public class LodQuadBuilder color = ColorUtil.applyShade(color, MC.getShade(quad.direction)); } } - } + } } } @@ -291,7 +406,6 @@ public class LodQuadBuilder mx, my, mz); } } - private void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte normalIndex, byte irisBlockMaterialId, byte skylight, byte blocklight, int mx, int my, int mz) { skylight %= 16; @@ -332,389 +446,6 @@ public class LodQuadBuilder - //=================// - // data finalizing // - //=================// - - /** runs any final data cleanup, merging, etc. */ - public void finalizeData() { this.mergeQuads(); } - - /** Uses Greedy meshing to merge this builder's Quads. */ - public void mergeQuads() - { - long mergeCount = 0; - long preQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount(); - if (preQuadsCount <= 1) - { - return; - } - - for (int directionIndex = 0; directionIndex < 6; directionIndex++) - { - mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.EastWest); - if (this.doTransparency) - { - mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.EastWest); - } - - - // only run the second merge if the face is the top or bottom - if (directionIndex == EDhDirection.UP.ordinal() || directionIndex == EDhDirection.DOWN.ordinal()) - { - mergeCount += mergeQuadsInternal(this.opaqueQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown); - if (this.doTransparency) - { - mergeCount += mergeQuadsInternal(this.transparentQuads, directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown); - } - } - } - - long postQuadsCount = this.getCurrentOpaqueQuadsCount() + this.getCurrentTransparentQuadsCount(); - LOGGER.debug("Merged "+mergeCount+"/"+preQuadsCount+"("+(mergeCount / (double) preQuadsCount)+") quads"); - } - - /** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */ - private static long mergeQuadsInternal(ArrayList[] list, int directionIndex, BufferMergeDirectionEnum mergeDirection) - { - if (list[directionIndex].size() <= 1) - return 0; - - list[directionIndex].sort((objOne, objTwo) -> objOne.compare(objTwo, mergeDirection)); - - long mergeCount = 0; - ListIterator iter = list[directionIndex].listIterator(); - BufferQuad currentQuad = iter.next(); - while (iter.hasNext()) - { - BufferQuad nextQuad = iter.next(); - - if (currentQuad.tryMerge(nextQuad, mergeDirection)) - { - // merge successful, attempt to merge the next quad - mergeCount++; - iter.set(null); - } - else - { - // merge fail, move on to the next quad - currentQuad = nextQuad; - } - } - list[directionIndex].removeIf(Objects::isNull); - return mergeCount; - } - - - - //==============// - // buffer setup // - //==============// - - public Iterator makeOpaqueVertexBuffers() - { - return new Iterator() - { - final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER) - .order(ByteOrder.nativeOrder()); - int dir = skipEmpty(0); - int quad = 0; - - private int skipEmpty(int d) - { - while (d < 6 && opaqueQuads[d].isEmpty()) - { - d++; - } - return d; - } - - @Override - public boolean hasNext() - { - return dir < 6; - } - - @Override - public ByteBuffer next() - { - if (dir >= 6) - { - return null; - } - bb.clear(); - bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER); - while (bb.hasRemaining() && dir < 6) - { - writeData(); - } - bb.limit(bb.position()); - bb.rewind(); - return bb; - } - - private void writeData() - { - int i = quad; - for (; i < opaqueQuads[dir].size(); i++) - { - if (!bb.hasRemaining()) - { - break; - } - putQuad(bb, opaqueQuads[dir].get(i)); - } - - if (i >= opaqueQuads[dir].size()) - { - quad = 0; - dir++; - dir = skipEmpty(dir); - } - else - { - quad = i; - } - } - }; - } - - public Iterator makeTransparentVertexBuffers() - { - return new Iterator() - { - final ByteBuffer bb = ByteBuffer.allocateDirect(ColumnRenderBuffer.FULL_SIZED_BUFFER) - .order(ByteOrder.nativeOrder()); - int directionIndex = this.skipEmptyDirectionIndices(0); - int quad = 0; - - private int skipEmptyDirectionIndices(int directionIndex) - { - while (directionIndex < 6 && - (LodQuadBuilder.this.transparentQuads[directionIndex] == null - || LodQuadBuilder.this.transparentQuads[directionIndex].isEmpty())) - { - directionIndex++; - } - - return directionIndex; - } - - @Override - public boolean hasNext() { return this.directionIndex < 6; } - - @Override - public ByteBuffer next() - { - if (this.directionIndex >= 6) - { - return null; - } - - this.bb.clear(); - this.bb.limit(ColumnRenderBuffer.FULL_SIZED_BUFFER); - while (this.bb.hasRemaining() && this.directionIndex < 6) - { - this.writeData(); - } - this.bb.limit(this.bb.position()); - this.bb.rewind(); - return this.bb; - } - - private void writeData() - { - int i = this.quad; - for (; i < LodQuadBuilder.this.transparentQuads[this.directionIndex].size(); i++) - { - if (!this.bb.hasRemaining()) - { - break; - } - putQuad(this.bb, LodQuadBuilder.this.transparentQuads[this.directionIndex].get(i)); - } - - if (i >= LodQuadBuilder.this.transparentQuads[this.directionIndex].size()) - { - this.quad = 0; - this.directionIndex++; - this.directionIndex = this.skipEmptyDirectionIndices(this.directionIndex); - } - else - { - this.quad = i; - } - } - }; - } - public interface BufferFiller - { - /** If true: more data needs to be filled */ - boolean fill(GLVertexBuffer vbo); - - } - - public BufferFiller makeOpaqueBufferFiller(EDhApiGpuUploadMethod method) - { - return new BufferFiller() - { - int dir = 0; - int quad = 0; - - public boolean fill(GLVertexBuffer vbo) - { - if (dir >= 6) - { - vbo.setVertexCount(0); - return false; - } - - int numOfQuads = _countRemainingQuads(); - if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER) - numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER; - if (numOfQuads == 0) - { - vbo.setVertexCount(0); - return false; - } - ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method, - ColumnRenderBuffer.FULL_SIZED_BUFFER); - if (bb == null) - throw new NullPointerException("mapBuffer returned null"); - bb.clear(); - bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE); - while (bb.hasRemaining() && dir < 6) - { - writeData(bb); - } - bb.rewind(); - vbo.unmapBuffer(); - vbo.setVertexCount(numOfQuads * 4); - return dir < 6; - } - - private int _countRemainingQuads() - { - int a = opaqueQuads[dir].size() - quad; - for (int i = dir + 1; i < opaqueQuads.length; i++) - { - a += opaqueQuads[i].size(); - } - return a; - } - - private void writeData(ByteBuffer bb) - { - int startQ = quad; - - int i = startQ; - for (i = startQ; i < opaqueQuads[dir].size(); i++) - { - if (!bb.hasRemaining()) - { - break; - } - putQuad(bb, opaqueQuads[dir].get(i)); - } - - if (i >= opaqueQuads[dir].size()) - { - quad = 0; - dir++; - while (dir < 6 && opaqueQuads[dir].isEmpty()) - { - dir++; - } - } - else - { - quad = i; - } - } - }; - } - - public BufferFiller makeTransparentBufferFiller(EDhApiGpuUploadMethod method) - { - return new BufferFiller() - { - int dir = 0; - int quad = 0; - - public boolean fill(GLVertexBuffer vbo) - { - if (dir >= 6) - { - vbo.setVertexCount(0); - return false; - } - - int numOfQuads = _countRemainingQuads(); - if (numOfQuads > ColumnRenderBuffer.MAX_QUADS_PER_BUFFER) - numOfQuads = ColumnRenderBuffer.MAX_QUADS_PER_BUFFER; - if (numOfQuads == 0) - { - vbo.setVertexCount(0); - return false; - } - ByteBuffer bb = vbo.mapBuffer(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE, method, - ColumnRenderBuffer.FULL_SIZED_BUFFER); - if (bb == null) - throw new NullPointerException("mapBuffer returned null"); - bb.clear(); - bb.limit(numOfQuads * ColumnRenderBuffer.QUADS_BYTE_SIZE); - while (bb.hasRemaining() && dir < 6) - { - writeData(bb); - } - bb.rewind(); - vbo.unmapBuffer(); - vbo.setVertexCount(numOfQuads * 4); - return dir < 6; - } - - private int _countRemainingQuads() - { - int a = transparentQuads[dir].size() - quad; - for (int i = dir + 1; i < transparentQuads.length; i++) - { - a += transparentQuads[i].size(); - } - return a; - } - - private void writeData(ByteBuffer bb) - { - int startQ = quad; - - int i = startQ; - for (i = startQ; i < transparentQuads[dir].size(); i++) - { - if (!bb.hasRemaining()) - { - break; - } - putQuad(bb, transparentQuads[dir].get(i)); - } - - if (i >= transparentQuads[dir].size()) - { - quad = 0; - dir++; - while (dir < 6 && transparentQuads[dir].isEmpty()) - { - dir++; - } - } - else - { - quad = i; - } - } - }; - } - - - //=========// // getters // //=========// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java deleted file mode 100644 index 2594ab63d..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * This file is part of the Distant Horizons mod - * licensed under the GNU LGPL v3 License. - * - * Copyright (C) 2020-2023 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 . - */ - -package com.seibel.distanthorizons.core.dataObjects.transformers; - -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; - -import com.seibel.distanthorizons.core.config.Config; -import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; -import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; -import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; -import com.seibel.distanthorizons.core.pos.DhChunkPos; -import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; -import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; -import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; -import org.apache.logging.log4j.LogManager; - -public class ChunkToLodBuilder implements AutoCloseable -{ - public static final ConfigBasedLogger LOGGER = new ConfigBasedLogger(LogManager.getLogger(), () -> Config.Client.Advanced.Logging.logLodBuilderEvent.get()); - private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - - public static final long MAX_TICK_TIME_NS = 1000000000L / 20L; - - private final ConcurrentHashMap concurrentChunkToBuildByChunkPos = new ConcurrentHashMap<>(); - private final ConcurrentLinkedDeque concurrentTaskToBuildList = new ConcurrentLinkedDeque<>(); - private final AtomicInteger runningCount = new AtomicInteger(0); - - - - //==============// - // constructors // - //==============// - - public ChunkToLodBuilder() { } - - - - //=================// - // data generation // - //=================// - - public CompletableFuture tryGenerateData(IChunkWrapper chunkWrapper) - { - if (chunkWrapper == null) - { - throw new NullPointerException("ChunkWrapper cannot be null!"); - } - - IChunkWrapper oldChunk = this.concurrentChunkToBuildByChunkPos.put(chunkWrapper.getChunkPos(), chunkWrapper); // an Exchange operation - // If there's old chunk, that means we just replaced an unprocessed old request on generating data on this pos. - // if so, we can just return null to signal this, as the old request's future will instead be the proper one - // that will return the latest generated data. - if (oldChunk != null) - { - return null; - } - - // Otherwise, it means we're the first to do so. Let's submit our task to this entry. - CompletableFuture future = new CompletableFuture<>(); - this.concurrentTaskToBuildList.addLast(new Task(chunkWrapper.getChunkPos(), future)); - return future; - } - - // TODO why on tick? - public void tick() - { - int threadCount = ThreadPoolUtil.getWorkerThreadCount(); - if (this.runningCount.get() >= threadCount) - { - return; - } - else if (this.concurrentTaskToBuildList.isEmpty()) - { - return; - } - else if (MC != null && !MC.playerExists()) - { - // MC hasn't finished loading (or is currently unloaded) - - // can be uncommented if tasks aren't being cleared correctly - //this.clearCurrentTasks(); - return; - } - - ThreadPoolExecutor lodBuilderExecutor = ThreadPoolUtil.getChunkToLodBuilderExecutor(); - if (lodBuilderExecutor == null) - { - return; - } - - - for (int i = 0; i < threadCount; i++) - { - this.runningCount.incrementAndGet(); - try - { - CompletableFuture.runAsync(() -> - { - try - { - this.tickThreadTask(); - } - finally - { - this.runningCount.decrementAndGet(); - } - }, lodBuilderExecutor); - } - catch (RejectedExecutionException ignore) { /* the thread pool was probably shut down because it's size is being changed, just wait a sec and it should be back */ } - } - } - private void tickThreadTask() - { - long time = System.nanoTime(); - int count = 0; - boolean allDone = false; - while (true) - { - // run until we either run out of time, or all tasks are complete - if (System.nanoTime() - time > MAX_TICK_TIME_NS && !this.concurrentTaskToBuildList.isEmpty()) - { - break; - } - - Task task = this.concurrentTaskToBuildList.pollFirst(); - if (task == null) - { - allDone = true; - break; - } - - count++; - IChunkWrapper latestChunk = this.concurrentChunkToBuildByChunkPos.remove(task.chunkPos); // Basically an Exchange operation - if (latestChunk == null) - { - LOGGER.error("Somehow Task at " + task.chunkPos + " has latestChunk as null. Skipping task."); - task.future.complete(null); - continue; - } - - try - { - if (LodDataBuilder.canGenerateLodFromChunk(latestChunk)) - { - FullDataSourceV2 dataSource = LodDataBuilder.createGeneratedDataSource(latestChunk); - if (dataSource != null) - { - task.future.complete(dataSource); - continue; - } - } - else if (task.generationAttemptExpirationTimeMs < System.currentTimeMillis()) - { - // this task won't be re-queued - //LOGGER.trace("removed chunk "+task.chunkPos); - continue; - } - } - catch (Exception ex) - { - LOGGER.error("Error while processing Task at " + task.chunkPos, ex); - } - - // Failed to build due to chunk not meeting requirement, - // re-add it to the queue so it can be tested next time - IChunkWrapper casChunk = this.concurrentChunkToBuildByChunkPos.putIfAbsent(task.chunkPos, latestChunk); // CAS operation with expected=null - if (casChunk == null || latestChunk.isStillValid()) // That means CAS have been successful - { - this.concurrentTaskToBuildList.addLast(task); // Then add back the same old task. - } - else // Else, it means someone managed to sneak in a new gen request in this pos. Then lets drop this old task. - { - task.future.complete(null); - } - - count--; - } - - long time2 = System.nanoTime(); - if (!allDone) - { - //LOGGER.info("Completed {} tasks in {} in this tick", count, Duration.ofNanos(time2 - time)); - } - else if (count > 0) - { - //LOGGER.info("Completed all {} tasks in {}", count, Duration.ofNanos(time2 - time)); - } - } - - /** - * should be called whenever changing levels/worlds - * to prevent trying to generate LODs for chunk(s) that are no longer loaded - * (which can cause exceptions) - */ - public void clearCurrentTasks() - { - this.concurrentTaskToBuildList.clear(); - this.concurrentChunkToBuildByChunkPos.clear(); - } - - - - - //==============// - // base methods // - //==============// - - @Override - public void close() { this.clearCurrentTasks(); } - - - - //================// - // helper classes // - //================// - - private static class Task - { - public final DhChunkPos chunkPos; - public final CompletableFuture future; - /** This is tracked so impossible tasks can be removed from the queue */ - public long generationAttemptExpirationTimeMs = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10); - - Task(DhChunkPos chunkPos, CompletableFuture future) - { - this.chunkPos = chunkPos; - this.future = future; - } - - } - -} - diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java index 01497f84b..2ddf92177 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java @@ -178,6 +178,7 @@ public class FullDataToRenderDataTransformer HashSet blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(level.getLevelWrapper()); HashSet caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(level.getLevelWrapper()); + int caveCullingMaxY = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get() - level.getMinY(); boolean caveCullingEnabled = Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get() && ( @@ -196,7 +197,7 @@ public class FullDataToRenderDataTransformer int skylightToApplyToNextBlock = -1; int blocklightToApplyToNextBlock = -1; - int columnOffset = 0; + int renderDataIndex = 0; @@ -205,12 +206,13 @@ public class FullDataToRenderDataTransformer //==================================// // goes from the top down - for (int i = 0; i < fullColumnData.size(); i++) + for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++) { - long fullData = fullColumnData.getLong(i); + long fullData = fullColumnData.getLong(fullDataIndex); int bottomY = FullDataPointUtil.getBottomY(fullData); int blockHeight = FullDataPointUtil.getHeight(fullData); + int topY = bottomY + blockHeight; int id = FullDataPointUtil.getId(fullData); int blockLight = FullDataPointUtil.getBlockLight(fullData); int skyLight = FullDataPointUtil.getSkyLight(fullData); @@ -252,24 +254,26 @@ public class FullDataToRenderDataTransformer { if (caveCullingEnabled // assume this data point is underground if it has no sky-light - && skyLight == LodUtil.MIN_MC_LIGHT + && skyLight == LodUtil.MIN_MC_LIGHT + // ignore caves above a certain height to prevent floating islands from having walls underneath them + && topY < caveCullingMaxY // cave culling shouldn't happen when at the top of the world - && columnOffset != 0 + && renderDataIndex != 0 && fullDataIndex != 0 // cave culling can't happen when at the bottom of the world - && columnOffset != fullColumnData.size()) + && (fullDataIndex+1) < fullColumnData.size()) { // we need to get the next sky/block lights because // the air block here will always have a light of 0/0 due to only the top of the LOD's light being saved. - long nextFullData = fullColumnData.getLong(i+1); + long nextFullData = fullColumnData.getLong(fullDataIndex+1); int nextSkyLight = FullDataPointUtil.getSkyLight(nextFullData); if (nextSkyLight == LodUtil.MIN_MC_LIGHT && ColorUtil.getAlpha(lastColor) == 255) { // replace the previous block with new bottom - long columnData = renderColumnData.get(columnOffset - 1); + long columnData = renderColumnData.get(renderDataIndex - 1); columnData = RenderDataPointUtil.setYMin(columnData, bottomY); - renderColumnData.set(columnOffset - 1, columnData); + renderColumnData.set(renderDataIndex - 1, columnData); } continue; @@ -337,20 +341,20 @@ public class FullDataToRenderDataTransformer //=============================// // check if they share a top-bottom face and if they have same color - if (color == lastColor && bottomY + blockHeight == lastBottom && columnOffset > 0) + if (color == lastColor && bottomY + blockHeight == lastBottom && renderDataIndex > 0) { //replace the previous block with new bottom - long columnData = renderColumnData.get(columnOffset - 1); + long columnData = renderColumnData.get(renderDataIndex - 1); columnData = RenderDataPointUtil.setYMin(columnData, bottomY); - renderColumnData.set(columnOffset - 1, columnData); + renderColumnData.set(renderDataIndex - 1, columnData); } else { // add the block isColumnVoid = false; long columnData = RenderDataPointUtil.createDataPoint(bottomY + blockHeight, bottomY, color, skyLight, blockLight, block.getMaterialId()); - renderColumnData.set(columnOffset, columnData); - columnOffset++; + renderColumnData.set(renderDataIndex, columnData); + renderDataIndex++; } lastBottom = bottomY; lastColor = color; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java index 6b4d1861e..0cd1b2f12 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java @@ -320,6 +320,8 @@ public class LodDataBuilder // AND the below loop won't run. int size = (columnDataPoints != null) ? columnDataPoints.size() : 0; + // TODO make missing air LODs + // TODO merge duplicate datapoints LongArrayList packedDataPoints = new LongArrayList(new long[size]); for (int index = 0; index < size; index++) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java index 628aabf80..46517e95e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/structure/ClientOnlySaveStructure.java @@ -26,12 +26,12 @@ import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.api.enums.config.EDhApiServerFolderNameMode; import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel; import com.seibel.distanthorizons.core.util.objects.ParsedIp; -import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IDimensionTypeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import com.seibel.distanthorizons.coreapi.util.StringUtil; import java.io.File; import java.util.*; @@ -138,7 +138,7 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure { // use the first existing sub-dimension String folderName = folders.get(0).getName(); - LOGGER.info("Default Sub Dimension set to: [" + LodUtil.shortenString(folderName, 8) + "...]"); + LOGGER.info("Default Sub Dimension set to: [" + StringUtil.shortenString(folderName, 8) + "...]"); return folders.get(0); } else diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java b/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java index 841b0b2f8..ab72c2b2a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java @@ -40,6 +40,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import com.seibel.distanthorizons.coreapi.util.StringUtil; import it.unimi.dsi.fastutil.longs.LongArrayList; import org.apache.logging.log4j.LogManager; @@ -92,7 +93,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable if (potentialLevelFolders.size() == 0) { String newId = UUID.randomUUID().toString(); - LOGGER.info("No potential level files found. Creating a new sub dimension with the ID ["+LodUtil.shortenString(newId, 8)+"]..."); + LOGGER.info("No potential level files found. Creating a new sub dimension with the ID ["+ StringUtil.shortenString(newId, 8)+"]..."); this.foundLevelFile = this.CreateSubDimFolder(newId); } } @@ -207,7 +208,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable SubDimCompare mostSimilarSubDim = null; for (File testLevelFolder : this.potentialLevelFolders) { - LOGGER.info("Testing level folder: [" + LodUtil.shortenString(testLevelFolder.getName(), 8) + "]"); + LOGGER.info("Testing level folder: [" + StringUtil.shortenString(testLevelFolder.getName(), 8) + "]"); FullDataSourceV2 testFullDataSource = null; try @@ -328,8 +329,8 @@ public class SubDimensionLevelMatcher implements AutoCloseable } - String subDimShortName = LodUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging - String equalPercent = LodUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5); + String subDimShortName = StringUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging + String equalPercent = StringUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5); LOGGER.info("Sub dimension ["+subDimShortName+"...] is current dimension probability: "+equalPercent+" ("+equalDataPoints+"/"+totalDataPointCount+")"); } catch (Exception e) @@ -359,7 +360,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable { // we found a sub dim folder that is similar, use it - LOGGER.info("Sub Dimension set to: [" + LodUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]"); + LOGGER.info("Sub Dimension set to: [" + StringUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]"); return mostSimilarSubDim.folder; } else @@ -369,7 +370,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable String newId = UUID.randomUUID().toString(); double highestEqualityPercent = mostSimilarSubDim != null ? mostSimilarSubDim.getPercentEqual() : 0; - String message = "No suitable sub dimension found. The highest equality was [" + LodUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + LodUtil.shortenString(newId, 8) + "..."; + String message = "No suitable sub dimension found. The highest equality was [" + StringUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + StringUtil.shortenString(newId, 8) + "..."; LOGGER.info(message); File folder = this.CreateSubDimFolder(newId); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java index c160ef482..901aa8583 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java @@ -147,7 +147,7 @@ public class DhLightingEngine { for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++) { - // set each pos' sky light all the way down until a opaque block is hit + // set each pos' sky light all the way down until an opaque block is hit for (int y = maxY; y >= minY; y--) { IBlockStateWrapper block = chunk.getBlockState(relX, y, relZ); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java index 1c3b1e08d..f6f464b83 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java @@ -19,28 +19,19 @@ package com.seibel.distanthorizons.core.level; -import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup; import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkModifiedEvent; -import com.seibel.distanthorizons.api.objects.math.DhApiVec3f; -import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox; -import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading; -import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; -import com.seibel.distanthorizons.core.dataObjects.transformers.ChunkToLodBuilder; import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; -import com.seibel.distanthorizons.core.pos.DhBlockPos; import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler; import com.seibel.distanthorizons.core.render.renderer.generic.CloudRenderHandler; import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer; -import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO; import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo; import com.seibel.distanthorizons.core.sql.repo.ChunkHashRepo; -import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import org.apache.logging.log4j.Logger; @@ -59,8 +50,6 @@ public abstract class AbstractDhLevel implements IDhLevel { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - public final ChunkToLodBuilder chunkToLodBuilder; - /** if this is null then the other handler is probably null too, but just in case */ @Nullable public ChunkHashRepo chunkHashRepo; @@ -71,6 +60,7 @@ public abstract class AbstractDhLevel implements IDhLevel protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 2_000); /** contains the {@link DhChunkPos} for each {@link DhSectionPos} that are queued to save via {@link AbstractDhLevel#delayedFullDataSourceSaveCache} */ protected final ConcurrentHashMap> updatedChunkPosSetBySectionPos = new ConcurrentHashMap<>(); + protected final ConcurrentHashMap updatedChunkHashesByChunkPos = new ConcurrentHashMap<>(); /** Will be null if clouds shouldn't be rendered for this level. */ @Nullable @@ -83,10 +73,7 @@ public abstract class AbstractDhLevel implements IDhLevel // constructor // //=============// - protected AbstractDhLevel() - { - this.chunkToLodBuilder = new ChunkToLodBuilder(); - } + protected AbstractDhLevel() { } /** * Creating the repos requires access to the level file, which isn't @@ -152,7 +139,7 @@ public abstract class AbstractDhLevel implements IDhLevel public int getUnsavedDataSourceCount() { return this.delayedFullDataSourceSaveCache.getUnsavedCount(); } @Override - public void updateChunkAsync(IChunkWrapper chunkWrapper) + public void updateChunkAsync(IChunkWrapper chunkWrapper, int chunkHash) { FullDataSourceV2 dataSource = FullDataSourceV2.createFromChunk(chunkWrapper); if (dataSource == null) @@ -171,6 +158,7 @@ public abstract class AbstractDhLevel implements IDhLevel chunkPosSet.add(chunkWrapper.getChunkPos()); return chunkPosSet; }); + this.updatedChunkHashesByChunkPos.put(chunkWrapper.getChunkPos(), chunkHash); // batch updates to reduce overhead when flying around or breaking/placing a lot of blocks in an area this.delayedFullDataSourceSaveCache.queueDataSourceForUpdateAndSave(dataSource); @@ -185,6 +173,13 @@ public abstract class AbstractDhLevel implements IDhLevel { for (DhChunkPos chunkPos : updatedChunkPosSet) { + // save after the data source has been updated to prevent saving the hash without the associated datasource + Integer chunkHash = this.updatedChunkHashesByChunkPos.remove(chunkPos); + if (this.chunkHashRepo != null && chunkHash != null) + { + this.chunkHashRepo.save(new ChunkHashDTO(chunkPos, chunkHash)); + } + ApiEventInjector.INSTANCE.fireAllEvents( DhApiChunkModifiedEvent.class, new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.x, chunkPos.z)); @@ -212,14 +207,6 @@ public abstract class AbstractDhLevel implements IDhLevel ChunkHashDTO dto = this.chunkHashRepo.getByKey(pos); return (dto != null) ? dto.chunkHash : 0; } - @Override - public void setChunkHash(DhChunkPos pos, int chunkHash) - { - if (this.chunkHashRepo != null) - { - this.chunkHashRepo.save(new ChunkHashDTO(pos, chunkHash)); - } - } @@ -262,8 +249,6 @@ public abstract class AbstractDhLevel implements IDhLevel @Override public void close() { - this.chunkToLodBuilder.close(); - if (this.chunkHashRepo != null) { this.chunkHashRepo.close(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java index 50aa62a7b..b6dfc266b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java @@ -164,7 +164,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel { try { - this.chunkToLodBuilder.tick(); this.clientside.clientTick(); if (this.dataRefreshQueue != null) 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 700826fd8..28a70d9e3 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 @@ -95,7 +95,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev { this.clientside.renderDeferred(renderEventParam, profiler); } @Override - public void serverTick() { this.chunkToLodBuilder.tick(); } + public void serverTick() { } @Override public void doWorldGen() diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index 437c4a930..bcc701439 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -265,8 +265,6 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel @Override public void serverTick() { - this.chunkToLodBuilder.tick(); - // Send finished data source requests for (Map.Entry entry : this.requestGroupsByPos.entrySet()) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java index 15b392a6c..013e05f67 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/IDhLevel.java @@ -45,8 +45,7 @@ public interface IDhLevel extends AutoCloseable /** @return 0 if no hash is known */ int getChunkHash(DhChunkPos pos); - void setChunkHash(DhChunkPos pos, int chunkHash); - void updateChunkAsync(IChunkWrapper chunk); + void updateChunkAsync(IChunkWrapper chunk, int newChunkHash); void loadBeaconBeamsInPos(long pos); void setBeaconBeamsForChunk(DhChunkPos chunkPos, List beamList); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java b/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java index 3cba37e27..03519f975 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/logging/f3/F3Screen.java @@ -73,6 +73,8 @@ public class F3Screen ThreadPoolExecutor worldGenPool = ThreadPoolUtil.getWorldGenExecutor(); ThreadPoolExecutor fileHandlerPool = ThreadPoolUtil.getFileHandlerExecutor(); ThreadPoolExecutor updatePool = ThreadPoolUtil.getUpdatePropagatorExecutor(); + ThreadPoolExecutor bufferBuilderPool = ThreadPoolUtil.getBufferBuilderExecutor(); + ThreadPoolExecutor bufferUploaderPool = ThreadPoolUtil.getBufferUploaderExecutor(); AbstractDhWorld world = SharedApi.getAbstractDhWorld(); Iterable levelIterator = world.getAllLoadedLevels(); @@ -89,6 +91,8 @@ public class F3Screen messageList.add(getThreadPoolStatString("World Gen", worldGenPool));//"World Gen Tasks: 40/5304, (in progress: 7)"); messageList.add(getThreadPoolStatString("File Handler", fileHandlerPool)); messageList.add(getThreadPoolStatString("Update Propagator", updatePool)); + messageList.add(getThreadPoolStatString("Buffer Builder", bufferBuilderPool)); + messageList.add(getThreadPoolStatString("Buffer Uploader", bufferUploaderPool)); messageList.add(""); // chunk updates messageList.add(SharedApi.INSTANCE.getDebugMenuString()); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java b/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java index e55072dbd..b7ac06d40 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/pos/DhSectionPos.java @@ -34,11 +34,11 @@ import java.util.function.LongConsumer; * For the specifics of how they compare can be viewed in the constants {@link #SECTION_BLOCK_DETAIL_LEVEL}, * {@link #SECTION_CHUNK_DETAIL_LEVEL}, and {@link #SECTION_REGION_DETAIL_LEVEL}).

* - * Why does the smallest render section represent 2x2 MC chunks (section detail level 6)?
+ * Why does the smallest render section represent 4x4 MC chunks (section detail level 6)?
* A section defines what unit the quad tree works in, because of that we don't want that unit to be too big or too small.
* Too small, and we'll have 1,000s of sections running around, all needing individual files and render buffers.
* Too big, and the LOD dropoff will be very noticeable.
- * With those thoughts in mind we decided on a smallest section size of 32 data points square (IE 2x2 chunks). + * With those thoughts in mind we decided on a smallest section size of 64 data points square (IE 4x4 chunks). * * @author Leetom */ diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/DhFrustumBounds.java b/core/src/main/java/com/seibel/distanthorizons/core/render/DhFrustumBounds.java index 0ef0b5f56..a4ca46ca2 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/DhFrustumBounds.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/DhFrustumBounds.java @@ -54,10 +54,6 @@ public class DhFrustumBounds implements IDhApiCullingFrustum Vector3f lodMin = new Vector3f(lodBlockPosMinX, this.worldMinY, lodBlockPosMinZ); Vector3f lodMax = new Vector3f(lodBlockPosMinX + lodBlockWidth, this.worldMaxY, lodBlockPosMinZ + lodBlockWidth); - if (lodMax.x < this.boundsMin.x || lodMin.x > this.boundsMax.x) return false; - if (lodMax.z < this.boundsMin.z || lodMin.z > this.boundsMax.z) return false; - if (this.worldMaxY < this.boundsMin.y || this.worldMinY > this.boundsMax.y) return false; - return this.frustum.testAab(lodMin, lodMax); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java index a78bc1841..343938287 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java @@ -189,7 +189,7 @@ public class LodQuadTree extends QuadTree implements IDebugRen try { LodRenderSection renderSection = this.getValue(pos); - if (renderSection != null && renderSection.renderingEnabled) + if (renderSection != null && renderSection.getRenderingEnabled()) { renderSection.uploadRenderDataToGpuAsync(); } @@ -296,7 +296,7 @@ public class LodQuadTree extends QuadTree implements IDebugRen if (DhSectionPos.getDetailLevel(sectionPos) > expectedDetailLevel) { // section detail level too high // - boolean thisPosIsRendering = renderSection.renderingEnabled; + boolean thisPosIsRendering = renderSection.getRenderingEnabled(); boolean allChildrenSectionsAreLoaded = true; // recursively update all child render sections @@ -314,24 +314,8 @@ public class LodQuadTree extends QuadTree implements IDebugRen } else { - if (renderSection.renderingEnabled - && Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get()) - { - // show that this position has just been disabled - DebugRenderer.makeParticle( - new DebugRenderer.BoxParticle( - new DebugRenderer.Box(renderSection.pos, 128f, 156f, 0.09f, Color.CYAN.darker()), - 0.2, 32f - ) - ); - } - // all child positions are loaded, disable this section and enable its children. - if (renderSection.renderingEnabled) - { - this.level.unloadBeaconBeamsInPos(renderSection.pos); - } - renderSection.renderingEnabled = false; + renderSection.setRenderingEnabled(false); // walk back down the tree and enable the child sections //TODO there are probably more efficient ways of doing this, but this will work for now for (int i = 0; i < 4; i++) @@ -382,17 +366,14 @@ public class LodQuadTree extends QuadTree implements IDebugRen if (!parentSectionIsRendering && renderSection.canRender()) { // if rendering is already enabled we don't have to re-enable it - if (!renderSection.renderingEnabled) + if (!renderSection.getRenderingEnabled()) { - renderSection.renderingEnabled = true; - this.level.loadBeaconBeamsInPos(renderSection.pos); - // delete/disable children, all of them will be a lower detail level than requested quadNode.deleteAllChildren((childRenderSection) -> { if (childRenderSection != null) { - if (childRenderSection.renderingEnabled) + if (childRenderSection.getRenderingEnabled()) { // show that this position's rendering has been disabled due to a parent rendering DebugRenderer.makeParticle( @@ -403,10 +384,12 @@ public class LodQuadTree extends QuadTree implements IDebugRen ); } - childRenderSection.renderingEnabled = false; + childRenderSection.setRenderingEnabled(false); childRenderSection.close(); } }); + + renderSection.setRenderingEnabled(true); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java index 8a58e76f5..ff571bfad 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java @@ -23,6 +23,7 @@ import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBufferBuilder; +import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodQuadBuilder; import com.seibel.distanthorizons.core.dataObjects.transformers.FullDataToRenderDataTransformer; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.enums.EDhDirection; @@ -69,7 +70,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable private final LodQuadTree quadTree; - public boolean renderingEnabled = false; + private boolean renderingEnabled = false; private boolean canRender = false; /** this reference is necessary so we can determine what VBO to render */ @@ -80,7 +81,19 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable * Encapsulates everything between pulling data from the database (including neighbors) * up to the point when geometry data is uploaded to the GPU. */ - private CompletableFuture uploadRenderDataToGpuFuture = null; + private CompletableFuture buildAndUploadRenderDataToGpuFuture = null; + /** + * Represents just building the {@link LodQuadBuilder}.
+ * Separate from {@link LodRenderSection#bufferUploadFuture} because they run on + * different thread pools and need to be canceled separately. + */ + private CompletableFuture bufferBuildFuture = null; + /** + * Represents just uploading the {@link LodQuadBuilder} to the GPU.
+ * Separate from {@link LodRenderSection#bufferBuildFuture} because they run on + * different thread pools and need to be canceled separately. + */ + private CompletableFuture bufferUploadFuture = null; private final ReentrantLock getRenderSourceLock = new ReentrantLock(); /** Stored as a class variable so we can reuse it's result across multiple LOD loads if necessary */ @@ -114,6 +127,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable // render data loading/uploading // //===============================// + // TODO cleanup, there's a lot of nested futures and duplicate error handling here and it's hard to read public synchronized void uploadRenderDataToGpuAsync() { if (!GLProxy.hasInstance()) @@ -123,7 +137,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable return; } - if (this.uploadRenderDataToGpuFuture != null) + if (this.buildAndUploadRenderDataToGpuFuture != null) { // don't accidentally queue multiple uploads at the same time return; @@ -137,7 +151,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable return; } - this.uploadRenderDataToGpuFuture = CompletableFuture.runAsync(() -> + this.buildAndUploadRenderDataToGpuFuture = CompletableFuture.runAsync(() -> { //==================// // load render data // @@ -145,15 +159,15 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable this.tryDecrementingLoadFutureArray(this.adjacentLoadRefFutures); - ReferencedFutureWrapper thisLoadFuture = this.getRenderSourceAsync(); - ReferencedFutureWrapper[] adjLoadRefFutures = this.getNeighborRenderSourcesAsync(); + ReferencedFutureWrapper thisRenderSourceLoadFuture = this.getRenderSourceAsync(); + ReferencedFutureWrapper[] adjRenderSourceLoadRefFutures = this.getNeighborRenderSourcesAsync(); // wait for all futures to complete together, // merging the futures makes loading significantly faster than loading this position then loading its neighbors ArrayList> futureList = new ArrayList<>(); - futureList.add(thisLoadFuture.future); - for (ReferencedFutureWrapper refFuture : adjLoadRefFutures) + futureList.add(thisRenderSourceLoadFuture.future); + for (ReferencedFutureWrapper refFuture : adjRenderSourceLoadRefFutures) { futureList.add(refFuture.future); } @@ -162,73 +176,107 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable { try { - ColumnRenderSource renderSource = thisLoadFuture.future.get(); + ColumnRenderSource renderSource = thisRenderSourceLoadFuture.future.get(); if (renderSource == null || renderSource.isEmpty()) { - thisLoadFuture.decrementRefCount(); - for (ReferencedFutureWrapper futureWrapper : adjLoadRefFutures) + thisRenderSourceLoadFuture.decrementRefCount(); + for (ReferencedFutureWrapper futureWrapper : adjRenderSourceLoadRefFutures) { futureWrapper.decrementRefCount(); } // nothing needs to be rendered this.canRender = false; - this.uploadRenderDataToGpuFuture = null; + this.buildAndUploadRenderDataToGpuFuture = null; + this.bufferBuildFuture = null; return; } - //==============================// - // build/upload new render data // - //==============================// + //=======================// + // build new render data // + //=======================// try { ColumnRenderBuffer previousBuffer = this.renderBuffer; ColumnRenderSource[] adjacentRenderSections = new ColumnRenderSource[EDhDirection.ADJ_DIRECTIONS.length]; + boolean[] adjIsSameDetailLevel = new boolean[EDhDirection.ADJ_DIRECTIONS.length]; for (int i = 0; i < EDhDirection.ADJ_DIRECTIONS.length; i++) { - adjacentRenderSections[i] = adjLoadRefFutures[i].future.getNow(null); + adjacentRenderSections[i] = adjRenderSourceLoadRefFutures[i].future.getNow(null); + + // if the adjacent position isn't the same detail level the buffer building logic + // will need to be slightly different in order to reduce holes in the LODs + EDhDirection direction = EDhDirection.ADJ_DIRECTIONS[i]; + adjIsSameDetailLevel[direction.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(direction); } - ColumnRenderBufferBuilder.buildAndUploadBuffersAsync(this.level, renderSource, adjacentRenderSections) - .thenAccept((buffer) -> + + if (this.bufferBuildFuture != null) + { + // shouldn't normally happen, but just in case canceling the previous future + // prevents the CPU from working on something that won't be used + this.bufferBuildFuture.cancel(true); + } + this.bufferBuildFuture = ColumnRenderBufferBuilder.buildBuffersAsync(this.level, renderSource, adjacentRenderSections, adjIsSameDetailLevel); + this.bufferBuildFuture.thenAccept((lodQuadBuilder) -> { - // upload complete, clean up the old data if - this.renderBuffer = buffer; - this.canRender = (buffer != null); - this.uploadRenderDataToGpuFuture = null; - if (previousBuffer != null) + + //===================================// + // upload new render data to the GPU // + //===================================// + + if (this.bufferUploadFuture != null) { - previousBuffer.close(); + // shouldn't normally happen, but just in case canceling the previous future + // prevents the CPU from working on something that won't be used + this.bufferUploadFuture.cancel(true); } - - thisLoadFuture.decrementRefCount(); - this.tryDecrementingLoadFutureArray(adjLoadRefFutures); - this.adjacentLoadRefFutures = null; + this.bufferUploadFuture = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, renderSource, lodQuadBuilder); + this.bufferUploadFuture.thenAccept((buffer) -> + { + // upload complete, clean up the old data if + this.renderBuffer = buffer; + this.canRender = (buffer != null); + this.buildAndUploadRenderDataToGpuFuture = null; + this.bufferBuildFuture = null; + + + if (previousBuffer != null) + { + previousBuffer.close(); + } + + thisRenderSourceLoadFuture.decrementRefCount(); + this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures); + this.adjacentLoadRefFutures = null; + }); }); } catch (Exception e) { - thisLoadFuture.decrementRefCount(); - this.tryDecrementingLoadFutureArray(adjLoadRefFutures); + thisRenderSourceLoadFuture.decrementRefCount(); + this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures); this.adjacentLoadRefFutures = null; LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e); - this.uploadRenderDataToGpuFuture = null; + this.buildAndUploadRenderDataToGpuFuture = null; + this.bufferBuildFuture = null; } } catch (Exception e) { - thisLoadFuture.decrementRefCount(); - this.tryDecrementingLoadFutureArray(adjLoadRefFutures); + thisRenderSourceLoadFuture.decrementRefCount(); + this.tryDecrementingLoadFutureArray(adjRenderSourceLoadRefFutures); this.adjacentLoadRefFutures = null; LOGGER.error("Unexpected error in LodRenderSection loading, Error: "+e.getMessage(), e); - this.uploadRenderDataToGpuFuture = null; + this.buildAndUploadRenderDataToGpuFuture = null; + this.bufferBuildFuture = null; } }); }, executor); @@ -245,17 +293,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable long adjPos = DhSectionPos.getAdjacentPos(this.pos, direction); try { - // ignore adjacent positions that aren't the same detail level - // since the LodDataBuilder can't handle different detail levels - byte detailLevel = this.quadTree.calculateExpectedDetailLevel(new DhBlockPos2D(MC.getPlayerBlockPos()), adjPos); - detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; - if (detailLevel == DhSectionPos.getDetailLevel(this.pos)) + LodRenderSection adjRenderSection = this.quadTree.getValue(adjPos); + if (adjRenderSection != null) { - LodRenderSection adjRenderSection = this.quadTree.getValue(adjPos); - if (adjRenderSection != null) - { - futureArray[arrayIndex] = adjRenderSection.getRenderSourceAsync(); - } + futureArray[arrayIndex] = adjRenderSection.getRenderSourceAsync(); } } catch (IndexOutOfBoundsException ignore) {} @@ -316,6 +357,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable this.getRenderSourceLock.unlock(); } } + private boolean isAdjacentPosSameDetailLevel(EDhDirection direction) + { + long adjPos = DhSectionPos.getAdjacentPos(this.pos, direction); + byte detailLevel = this.quadTree.calculateExpectedDetailLevel(new DhBlockPos2D(MC.getPlayerBlockPos()), adjPos); + detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; + boolean adjacentIsSameDetailLevel = (detailLevel == DhSectionPos.getDetailLevel(this.pos)); + return adjacentIsSameDetailLevel; + } /** @@ -324,8 +373,9 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable */ public void cancelGpuUpload() { - CompletableFuture future = this.uploadRenderDataToGpuFuture; - this.uploadRenderDataToGpuFuture = null; + CompletableFuture future = this.buildAndUploadRenderDataToGpuFuture; + this.buildAndUploadRenderDataToGpuFuture = null; + this.bufferBuildFuture = null; if (future != null) { // interrupting the future speeds things up, but also causes @@ -342,7 +392,39 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable public boolean canRender() { return this.canRender; } - public boolean gpuUploadInProgress() { return this.uploadRenderDataToGpuFuture != null; } + public boolean getRenderingEnabled() { return this.renderingEnabled; } + public void setRenderingEnabled(boolean enabled) + { + // some logic should only be run when enabling/disabling + // a section for the first time + boolean stateChanged = (this.renderingEnabled != enabled); + if (stateChanged) + { + if (enabled) + { + this.level.loadBeaconBeamsInPos(this.pos); + } + else + { + this.level.unloadBeaconBeamsInPos(this.pos); + + if (Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get()) + { + // show that this position has just been disabled + DebugRenderer.makeParticle( + new DebugRenderer.BoxParticle( + new DebugRenderer.Box(this.pos, 128f, 156f, 0.09f, Color.CYAN.darker()), + 0.2, 32f + ) + ); + } + } + } + + this.renderingEnabled = enabled; + } + + public boolean gpuUploadInProgress() { return this.buildAndUploadRenderDataToGpuFuture != null; } @@ -452,9 +534,18 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable this.renderBuffer.close(); } - if (this.uploadRenderDataToGpuFuture != null) + // cancel all in-progress futures since they aren't needed any more + if (this.buildAndUploadRenderDataToGpuFuture != null) { - this.uploadRenderDataToGpuFuture.cancel(true); + this.buildAndUploadRenderDataToGpuFuture.cancel(true); + } + if (this.bufferBuildFuture != null) + { + this.bufferBuildFuture.cancel(true); + } + if (this.bufferUploadFuture != null) + { + this.bufferUploadFuture.cancel(true); } // this render section won't be rendering, we don't need to load any data for it @@ -489,7 +580,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable { color = Color.green; } - else if (this.uploadRenderDataToGpuFuture != null) + else if (this.buildAndUploadRenderDataToGpuFuture != null) { color = Color.yellow; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/RenderBufferHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderBufferHandler.java index d02a238fb..aa2c2ed22 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/RenderBufferHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/RenderBufferHandler.java @@ -311,7 +311,7 @@ public class RenderBufferHandler implements AutoCloseable } ColumnRenderBuffer buffer = renderSection.renderBuffer; - if (buffer == null || !renderSection.renderingEnabled) + if (buffer == null || !renderSection.getRenderingEnabled()) { continue; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/GLProxy.java b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/GLProxy.java index 4bbfbd6e8..c5556bd9d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/GLProxy.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/GLProxy.java @@ -193,17 +193,7 @@ public class GLProxy return instance; } - public EDhApiGpuUploadMethod getGpuUploadMethod() - { - EDhApiGpuUploadMethod method = Config.Client.Advanced.GpuBuffers.gpuUploadMethod.get(); - if (!this.bufferStorageSupported && method == EDhApiGpuUploadMethod.BUFFER_STORAGE) - { - // if buffer storage isn't supported - // default to DATA since that is the most compatible - method = EDhApiGpuUploadMethod.DATA; - } - return method == EDhApiGpuUploadMethod.AUTO ? this.preferredUploadMethod : method; - } + public EDhApiGpuUploadMethod getGpuUploadMethod() { return this.preferredUploadMethod; } public boolean runningOnRenderThread() { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLBuffer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLBuffer.java index bdfee1f4b..c90ebc1c0 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLBuffer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLBuffer.java @@ -238,7 +238,7 @@ public class GLBuffer implements AutoCloseable // buffer mapping // //================// - public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpensionSize, int bufferHint, int mapFlags) + public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpansionSize, int bufferHint, int mapFlags) { LodUtil.assertTrue(targetSize != 0, "MapBuffer targetSize is 0"); LodUtil.assertTrue(uploadMethod.useEarlyMapping, "Upload method must be one that use early mappings in order to call mapBuffer"); @@ -252,7 +252,7 @@ public class GLBuffer implements AutoCloseable if (this.size < targetSize || this.size > targetSize * BUFFER_SHRINK_TRIGGER) { int newSize = (int) (targetSize * BUFFER_EXPANSION_MULTIPLIER); - if (newSize > maxExpensionSize) newSize = maxExpensionSize; + if (newSize > maxExpansionSize) newSize = maxExpansionSize; this.size = newSize; if (this.bufferStorage) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLVertexBuffer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLVertexBuffer.java index f3957ef56..5c5586e61 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLVertexBuffer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/GLVertexBuffer.java @@ -77,9 +77,9 @@ public class GLVertexBuffer extends GLBuffer this.vertexCount = vertCount; } - public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpensionSize) + public ByteBuffer mapBuffer(int targetSize, EDhApiGpuUploadMethod uploadMethod, int maxExpansionSize) { - return super.mapBuffer(targetSize, uploadMethod, maxExpensionSize, + return super.mapBuffer(targetSize, uploadMethod, maxExpansionSize, uploadMethod.useBufferStorage ? GL32.GL_MAP_WRITE_BIT : uploadMethod.useEarlyMapping ? GL32.GL_DYNAMIC_DRAW : GL32.GL_STATIC_DRAW, GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/QuadElementBuffer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/QuadElementBuffer.java index 915d70c3d..f01ff0abd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/QuadElementBuffer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/glObject/buffer/QuadElementBuffer.java @@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.render.glObject.GLEnums; import com.seibel.distanthorizons.core.render.glObject.GLProxy; import org.apache.logging.log4j.Logger; import org.lwjgl.opengl.GL32; +import org.lwjgl.system.MemoryUtil; import java.lang.invoke.MethodHandles; import java.nio.ByteBuffer; @@ -154,7 +155,7 @@ public class QuadElementBuffer extends GLElementBuffer LOGGER.info("Quad IBO Resizing from [" + getCapacity() + "] to [" + quadCount + "]" + " with type: " + GLEnums.getString(type)); - ByteBuffer buffer = ByteBuffer.allocateDirect(indicesCount * GLEnums.getTypeSize(type)).order(ByteOrder.nativeOrder()); + ByteBuffer buffer = MemoryUtil.memAlloc(indicesCount * GLEnums.getTypeSize(type)); buildBuffer(quadCount, buffer, type); if (!gl.bufferStorageSupported) { @@ -169,6 +170,8 @@ public class QuadElementBuffer extends GLElementBuffer super.uploadBuffer(buffer, EDhApiGpuUploadMethod.BUFFER_STORAGE, indicesCount * GLEnums.getTypeSize(type), 0); } + + MemoryUtil.memFree(buffer); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/ScreenQuad.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/ScreenQuad.java index 2f9abd294..b551ffcdd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/ScreenQuad.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/ScreenQuad.java @@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer; import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.AbstractVertexAttribute; import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexPointer; import org.lwjgl.opengl.GL32; +import org.lwjgl.system.MemoryUtil; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -86,14 +87,14 @@ public class ScreenQuad private void createBuffer() { - ByteBuffer buffer = ByteBuffer.allocateDirect(box_vertices.length * Float.BYTES); - buffer.order(ByteOrder.nativeOrder()); + ByteBuffer buffer = MemoryUtil.memAlloc(box_vertices.length * Float.BYTES); buffer.asFloatBuffer().put(box_vertices); buffer.rewind(); this.boxBuffer = new GLVertexBuffer(false); this.boxBuffer.bind(); this.boxBuffer.uploadBuffer(buffer, box_vertices.length, EDhApiGpuUploadMethod.DATA, box_vertices.length * Float.BYTES); + MemoryUtil.memFree(buffer); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java index d1beb0701..71b991155 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/BeaconRenderHandler.java @@ -22,7 +22,6 @@ package com.seibel.distanthorizons.core.render.renderer.generic; import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial; import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup; import com.seibel.distanthorizons.api.objects.math.DhApiVec3d; -import com.seibel.distanthorizons.api.objects.math.DhApiVec3f; import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox; import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading; import com.seibel.distanthorizons.core.config.Config; @@ -42,7 +41,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; public class BeaconRenderHandler { @@ -56,7 +54,7 @@ public class BeaconRenderHandler private final BeaconBeamRepo beaconBeamRepo; private final IDhApiRenderableBoxGroup beaconBoxGroup; - private final HashMap beaconRefCountByBlockPos = new HashMap<>(); + private final HashSet beaconBlockPosSet = new HashSet<>(); @@ -96,8 +94,8 @@ public class BeaconRenderHandler for (int i = 0; i < newBeamList.size(); i++) { BeaconBeamDTO beam = newBeamList.get(i); - newBeamByPos.put(beam.pos, beam); - allPosSet.add(beam.pos); + newBeamByPos.put(beam.blockPos, beam); + allPosSet.add(beam.blockPos); } // get existing beams @@ -106,8 +104,8 @@ public class BeaconRenderHandler for (int i = 0; i < existingBeamList.size(); i++) { BeaconBeamDTO beam = existingBeamList.get(i); - existingBeamByPos.put(beam.pos, beam); - allPosSet.add(beam.pos); + existingBeamByPos.put(beam.blockPos, beam); + allPosSet.add(beam.blockPos); } @@ -168,7 +166,7 @@ public class BeaconRenderHandler for (int i = 0; i < existingBeamList.size(); i++) { BeaconBeamDTO beam = existingBeamList.get(i); - this.stopRenderingBeaconAtPos(beam.pos); + this.stopRenderingBeaconAtPos(beam.blockPos); } } @@ -180,50 +178,37 @@ public class BeaconRenderHandler private void startRenderingBeacon(BeaconBeamDTO beacon) { - this.beaconRefCountByBlockPos.compute(beacon.pos, (beamPos, beaconRefCount) -> + if (this.beaconBlockPosSet.add(beacon.blockPos)) { - if (beaconRefCount == null) { beaconRefCount = new AtomicInteger(); } - if (beaconRefCount.getAndIncrement() == 0) - { - DhApiRenderableBox beaconBox = new DhApiRenderableBox( - new DhApiVec3d(beacon.pos.x, beacon.pos.y+1, beacon.pos.z), - new DhApiVec3d(beacon.pos.x+1, BEAM_TOP_Y, beacon.pos.z+1), - beacon.color, - EDhApiBlockMaterial.ILLUMINATED - ); - - this.beaconBoxGroup.add(beaconBox); - this.beaconBoxGroup.triggerBoxChange(); - } - return beaconRefCount; - }); + DhApiRenderableBox beaconBox = new DhApiRenderableBox( + new DhApiVec3d(beacon.blockPos.x, beacon.blockPos.y+1, beacon.blockPos.z), + new DhApiVec3d(beacon.blockPos.x+1, BEAM_TOP_Y, beacon.blockPos.z+1), + beacon.color, + EDhApiBlockMaterial.ILLUMINATED + ); + + this.beaconBoxGroup.add(beaconBox); + this.beaconBoxGroup.triggerBoxChange(); + } } private void stopRenderingBeaconAtPos(DhBlockPos beaconPos) { - this.beaconRefCountByBlockPos.compute(beaconPos, (pos, beaconRefCount) -> + if (this.beaconBlockPosSet.remove(beaconPos)) { - if (beaconRefCount != null - && beaconRefCount.decrementAndGet() <= 0) + this.beaconBoxGroup.removeIf((box) -> { - this.beaconBoxGroup.removeIf((box) -> - box.minPos.x == beaconPos.x - && box.minPos.y == beaconPos.y+1 // plus 1 because the beam starts above the beacon - && box.minPos.z == beaconPos.z - ); - this.beaconBoxGroup.triggerBoxChange(); - return null; - } - else - { - return beaconRefCount; - } - }); + return box.minPos.x == beaconPos.x + && box.minPos.y == beaconPos.y+1 // plus 1 because the beam starts above the beacon + && box.minPos.z == beaconPos.z; + }); + this.beaconBoxGroup.triggerBoxChange(); + } } private void updateBeaconColor(BeaconBeamDTO newBeam) { - DhBlockPos pos = newBeam.pos; + DhBlockPos pos = newBeam.blockPos; for (int i = 0; i < this.beaconBoxGroup.size(); i++) { DhApiRenderableBox box = this.beaconBoxGroup.get(i); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/GenericObjectRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/GenericObjectRenderer.java index b3768548d..41baf1522 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/GenericObjectRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/generic/GenericObjectRenderer.java @@ -50,6 +50,7 @@ import org.apache.logging.log4j.Logger; import org.lwjgl.opengl.ARBInstancedArrays; import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL33; +import org.lwjgl.system.MemoryUtil; import java.awt.*; import java.nio.ByteBuffer; @@ -192,24 +193,22 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister private void createBuffers() { // box vertices - ByteBuffer boxVerticesBuffer = ByteBuffer.allocateDirect(BOX_VERTICES.length * Float.BYTES); - boxVerticesBuffer.order(ByteOrder.nativeOrder()); + ByteBuffer boxVerticesBuffer = MemoryUtil.memAlloc(BOX_VERTICES.length * Float.BYTES); boxVerticesBuffer.asFloatBuffer().put(BOX_VERTICES); boxVerticesBuffer.rewind(); this.boxVertexBuffer = new GLVertexBuffer(false); this.boxVertexBuffer.bind(); this.boxVertexBuffer.uploadBuffer(boxVerticesBuffer, 8, EDhApiGpuUploadMethod.DATA, BOX_VERTICES.length * Float.BYTES); - + MemoryUtil.memFree(boxVerticesBuffer); // box vertex indexes - ByteBuffer solidIndexBuffer = ByteBuffer.allocateDirect(BOX_INDICES.length * Integer.BYTES); - solidIndexBuffer.order(ByteOrder.nativeOrder()); + ByteBuffer solidIndexBuffer = MemoryUtil.memAlloc(BOX_INDICES.length * Integer.BYTES); solidIndexBuffer.asIntBuffer().put(BOX_INDICES); solidIndexBuffer.rewind(); this.boxIndexBuffer = new GLElementBuffer(false); this.boxIndexBuffer.uploadBuffer(solidIndexBuffer, EDhApiGpuUploadMethod.DATA, BOX_INDICES.length * Integer.BYTES, GL32.GL_STATIC_DRAW); this.boxIndexBuffer.bind(); - + MemoryUtil.memFree(solidIndexBuffer); } private void addGenericDebugObjects() { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/shaders/SSAOShader.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/shaders/SSAOShader.java index 05834e159..a625e2696 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/shaders/SSAOShader.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/shaders/SSAOShader.java @@ -103,17 +103,19 @@ public class SSAOShader extends AbstractShaderRenderer this.shader.setUniform(this.gSampleCountUniform, Config.Client.Advanced.Graphics.Ssao.sampleCount.get()); - this.shader.setUniform(this.gRadiusUniform, - Config.Client.Advanced.Graphics.Ssao.radius.get().floatValue()); + // Implicit Number cast needs to be done to prevent issues with the default value being a int + Number radius = Config.Client.Advanced.Graphics.Ssao.radius.get(); + this.shader.setUniform(this.gRadiusUniform, radius.floatValue()); - this.shader.setUniform(this.gStrengthUniform, - Config.Client.Advanced.Graphics.Ssao.strength.get().floatValue()); - this.shader.setUniform(this.gMinLightUniform, - Config.Client.Advanced.Graphics.Ssao.minLight.get().floatValue()); + Number strength = Config.Client.Advanced.Graphics.Ssao.strength.get(); + this.shader.setUniform(this.gStrengthUniform, strength.floatValue()); - this.shader.setUniform(this.gBiasUniform, - Config.Client.Advanced.Graphics.Ssao.bias.get().floatValue()); + Number minLight = Config.Client.Advanced.Graphics.Ssao.minLight.get(); + this.shader.setUniform(this.gMinLightUniform, minLight.floatValue()); + + Number bias = Config.Client.Advanced.Graphics.Ssao.bias.get(); + this.shader.setUniform(this.gBiasUniform, bias.floatValue()); GL32.glActiveTexture(GL32.GL_TEXTURE0); GL32.glBindTexture(GL32.GL_TEXTURE_2D, LodRenderer.getActiveDepthTextureId()); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/BeaconBeamDTO.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/BeaconBeamDTO.java index 452af3873..96fc63549 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/BeaconBeamDTO.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/BeaconBeamDTO.java @@ -21,14 +21,13 @@ package com.seibel.distanthorizons.core.sql.dto; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.pos.DhBlockPos; -import com.seibel.distanthorizons.core.pos.DhBlockPos; import java.awt.*; /** handles storing {@link FullDataSourceV2}'s in the database. */ public class BeaconBeamDTO implements IBaseDTO { - public DhBlockPos pos; + public DhBlockPos blockPos; public Color color; @@ -37,9 +36,9 @@ public class BeaconBeamDTO implements IBaseDTO // constructor // //=============// - public BeaconBeamDTO(DhBlockPos pos, Color color) + public BeaconBeamDTO(DhBlockPos blockPos, Color color) { - this.pos = pos; + this.blockPos = blockPos; this.color = color; } @@ -50,6 +49,6 @@ public class BeaconBeamDTO implements IBaseDTO //===========// @Override - public DhBlockPos getKey() { return this.pos; } + public DhBlockPos getKey() { return this.blockPos; } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/BeaconBeamRepo.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/BeaconBeamRepo.java index 9616a79b2..5651ae932 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/BeaconBeamRepo.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/BeaconBeamRepo.java @@ -100,9 +100,9 @@ public class BeaconBeamRepo extends AbstractDhRepo PreparedStatement statement = this.createPreparedStatement(sql); int i = 1; - statement.setObject(i++, dto.pos.x); - statement.setObject(i++, dto.pos.y); - statement.setObject(i++, dto.pos.z); + statement.setObject(i++, dto.blockPos.x); + statement.setObject(i++, dto.blockPos.y); + statement.setObject(i++, dto.blockPos.z); statement.setObject(i++, dto.color.getRed()); statement.setObject(i++, dto.color.getGreen()); @@ -132,9 +132,9 @@ public class BeaconBeamRepo extends AbstractDhRepo statement.setObject(i++, System.currentTimeMillis()); // last modified unix time - statement.setObject(i++, dto.pos.x); - statement.setObject(i++, dto.pos.y); - statement.setObject(i++, dto.pos.z); + statement.setObject(i++, dto.blockPos.x); + statement.setObject(i++, dto.blockPos.y); + statement.setObject(i++, dto.blockPos.z); return statement; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/LodUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/LodUtil.java index ad9de0b90..826ac5975 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/LodUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/LodUtil.java @@ -20,7 +20,6 @@ package com.seibel.distanthorizons.core.util; import java.util.Arrays; -import java.util.Iterator; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletionException; @@ -29,11 +28,8 @@ import java.util.concurrent.RejectedExecutionException; import com.seibel.distanthorizons.api.enums.config.EDhApiVanillaOverdraw; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; -import com.seibel.distanthorizons.core.pos.DhChunkPos; -import com.seibel.distanthorizons.core.pos.Pos2D; import com.seibel.distanthorizons.core.render.vertexFormat.DefaultLodVertexFormats; import com.seibel.distanthorizons.core.render.vertexFormat.LodVertexFormat; -import com.seibel.distanthorizons.core.util.gridList.EdgeDistanceBooleanGrid; import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; @@ -44,9 +40,6 @@ import org.apache.logging.log4j.Logger; /** * This class holds methods and constants that may be used in multiple places. - * - * @author James Seibel - * @version 2022-12-5 */ public class LodUtil { @@ -54,31 +47,7 @@ public class LodUtil private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - /** - * Vanilla render distances less than or equal to this will not allow partial - * overdraw. The VanillaOverdraw will either be ALWAYS or NEVER. - */ - public static final int MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW = 4; - /** - * Vanilla render distances less than or equal to this will cause the overdraw to - * run at a smaller fraction of the vanilla render distance. - */ - public static final int MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW = 11; - - - - - /** - * alpha used when drawing chunks in debug mode - */ - public static final int DEBUG_ALPHA = 255; // 0 - 25; - - public static final int COLOR_DEBUG_BLACK = ColorUtil.rgbToInt(DEBUG_ALPHA, 0, 0, 0); - public static final int COLOR_DEBUG_WHITE = ColorUtil.rgbToInt(DEBUG_ALPHA, 255, 255, 255); - public static final int COLOR_INVISIBLE = ColorUtil.rgbToInt(0, 0, 0, 0); - - //FIXME: WE NEED MORE COLORS!!!! /** * In order of nearest to farthest:
* Red, Orange, Yellow, Green, Cyan, Blue, Magenta, white, gray, black @@ -166,92 +135,9 @@ public class LodUtil - - /** - * Gets the ServerWorld for the relevant dimension. - * - * @return null if there is no ServerWorld for the given dimension - */ - public static ILevelWrapper getServerWorldFromDimension(IDimensionTypeWrapper newDimension) - { - if (!MC_CLIENT.hasSinglePlayerServer()) - return null; - - Iterable worlds = MC_CLIENT.getAllServerWorlds(); - ILevelWrapper returnWorld = null; - - for (ILevelWrapper world : worlds) - { - if (world.getDimensionType() == newDimension) - { - returnWorld = world; - break; - } - } - - return returnWorld; - } - - - public static int computeOverdrawOffset() - { - int chunkRenderDist = MC_RENDER.getRenderDistance() + 1; - EDhApiVanillaOverdraw overdraw = EDhApiVanillaOverdraw.ALWAYS; //Config.Client.Advanced.Graphics.AdvancedGraphics.vanillaOverdraw.get(); - if (overdraw == EDhApiVanillaOverdraw.ALWAYS) return Integer.MAX_VALUE; - - int offset; - if (overdraw == EDhApiVanillaOverdraw.NEVER) - { - offset = 0; //Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawOffset.get(); - } - else - { - if (chunkRenderDist < MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW) - { - offset = 1; - } - else - { - offset = chunkRenderDist / 5; - } - } - - if (chunkRenderDist - offset <= 1) - { - return Integer.MAX_VALUE; - } - return offset; - } - - /** not currently used since the new rendering system can't easily toggle single chunks to render */ - @Deprecated - public static EdgeDistanceBooleanGrid readVanillaRenderedChunks() - { - int offset = computeOverdrawOffset(); - if (offset == Integer.MAX_VALUE) return null; - int renderDist = MC_RENDER.getRenderDistance() + 1; - - Iterator posIter = MC_RENDER.getVanillaRenderedChunks().iterator(); - - return new EdgeDistanceBooleanGrid(new Iterator() - { - @Override - public boolean hasNext() - { - return posIter.hasNext(); - } - - @Override - public Pos2D next() - { - DhChunkPos pos = posIter.next(); - return new Pos2D(pos.x, pos.z); - } - }, - MC_CLIENT.getPlayerChunkPos().x - renderDist, - MC_CLIENT.getPlayerChunkPos().z - renderDist, - renderDist * 2 + 1); - } + //=========// + // methods // + //=========// /** Returns the chunk int position for the given double position */ public static int getChunkPosFromDouble(double value) { return (int) Math.floor(value / CHUNK_WIDTH); } @@ -275,11 +161,6 @@ public class LodUtil return true; } - public static void checkInterrupts() throws InterruptedException - { - if (Thread.interrupted()) throw new InterruptedException(); - } - /** * Format a given string with params using log4j's MessageFormat * @@ -290,26 +171,7 @@ public class LodUtil * Do not use it for deserialization or naming of objects. * @author leetom */ - public static String formatLog(String str, Object... param) - { - return LOGGER.getMessageFactory().newMessage(str, param).getFormattedMessage(); - } - - /** - * Returns a shortened version of the given string that is no longer than maxLength.
- * If null returns the empty string. - */ - public static String shortenString(String str, int maxLength) - { - if (str == null) - { - return ""; - } - else - { - return str.substring(0, Math.min(str.length(), maxLength)); - } - } + public static String formatLog(String str, Object... param) { return LOGGER.getMessageFactory().newMessage(str, param).getFormattedMessage(); } public static class AssertFailureException extends RuntimeException { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java index 4888f5673..91c1deade 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java @@ -48,93 +48,6 @@ public class RenderUtil - //=================// - // culling methods // - //=================// - - /** - * Returns if the given ChunkPos is in the loaded area of the world. - * - * @param center the center of the loaded world (probably the player's ChunkPos) - */ - public static boolean isChunkPosInLoadedArea(DhChunkPos pos, DhChunkPos center) - { - return (pos.x >= center.x - MC_RENDER.getRenderDistance() - && pos.x <= center.x + MC_RENDER.getRenderDistance()) - && - (pos.z >= center.z - MC_RENDER.getRenderDistance() - && pos.z <= center.z + MC_RENDER.getRenderDistance()); - } - - /** - * Returns if the given coordinate is in the loaded area of the world. - * - * @param centerCoordinate the center of the loaded world - */ - public static boolean isCoordinateInLoadedArea(int x, int z, int centerCoordinate) - { - return (x >= centerCoordinate - MC_RENDER.getRenderDistance() - && x <= centerCoordinate + MC_RENDER.getRenderDistance()) - && - (z >= centerCoordinate - MC_RENDER.getRenderDistance() - && z <= centerCoordinate + MC_RENDER.getRenderDistance()); - } - - /** - * Find the coordinates that are in the center half of the given - * 2D matrix, starting at (0,0) and going to (2 * lodRadius, 2 * lodRadius). - */ - public static boolean isCoordinateInNearFogArea(int i, int j, int lodRadius) - { - int halfRadius = lodRadius / 2; - - return (i >= lodRadius - halfRadius - && i <= lodRadius + halfRadius) - && - (j >= lodRadius - halfRadius - && j <= lodRadius + halfRadius); - } - - /** - * Returns true if one of the region's 4 corners is in front - * of the camera. - */ - public static boolean isRegionInViewFrustum(DhBlockPos playerBlockPos, Vec3f cameraDir, int vboRegionX, int vboRegionZ) - { - // convert the vbo position into a direction vector - // starting from the player's position - Vec3f vboVec = new Vec3f(vboRegionX * LodUtil.REGION_WIDTH, 0, vboRegionZ * LodUtil.REGION_WIDTH); - Vec3f playerVec = new Vec3f(playerBlockPos.x, playerBlockPos.y, playerBlockPos.z); - - vboVec.subtract(playerVec); - - // calculate the 4 corners - Vec3f vboSeVec = new Vec3f(vboVec.x + LodUtil.REGION_WIDTH, vboVec.y, vboVec.z + LodUtil.REGION_WIDTH); - Vec3f vboSwVec = new Vec3f(vboVec.x, vboVec.y, vboVec.z + LodUtil.REGION_WIDTH); - Vec3f vboNwVec = new Vec3f(vboVec.x, vboVec.y, vboVec.z); - Vec3f vboNeVec = new Vec3f(vboVec.x + LodUtil.REGION_WIDTH, vboVec.y, vboVec.z); - - // if any corner is visible, this region should be rendered - return isNormalizedVectorInViewFrustum(vboSeVec, cameraDir) || - isNormalizedVectorInViewFrustum(vboSwVec, cameraDir) || - isNormalizedVectorInViewFrustum(vboNwVec, cameraDir) || - isNormalizedVectorInViewFrustum(vboNeVec, cameraDir); - } - - /** - * Currently takes the dot product of the two vectors, - * but in the future could do more complicated frustum culling tests. - */ - private static boolean isNormalizedVectorInViewFrustum(Vec3f objectVector, Vec3f cameraDir) - { - // the -0.1 is to offer a slight buffer, so we are - // more likely to render LODs and thus, hopefully prevent - // flickering or odd disappearances - return objectVector.dotProduct(cameraDir) > -0.1; - } - - - //=====================// // matrix manipulation // //=====================// @@ -166,19 +79,6 @@ public class RenderUtil return mcModelViewMat.copy(); } - /** - * create and return a new combined modelView/projection matrix based on MC's modelView and projection matrices - * - * @param mcProjMat Minecraft's current projection matrix - * @param mcModelViewMat Minecraft's current model view matrix - */ - public static Mat4f createCombinedModelViewProjectionMatrix(Mat4f mcProjMat, Mat4f mcModelViewMat, float partialTicks) - { - Mat4f lodProj = createLodProjectionMatrix(mcProjMat, partialTicks); - lodProj.multiply(createLodModelViewMatrix(mcModelViewMat)); - return lodProj; - } - public static float getNearClipPlaneDistanceInBlocks(float partialTicks) { int chunkRenderDistance = MC_RENDER.getRenderDistance(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/gridList/EdgeDistanceBooleanGrid.java b/core/src/main/java/com/seibel/distanthorizons/core/util/gridList/EdgeDistanceBooleanGrid.java deleted file mode 100644 index c6f3dbcd5..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/gridList/EdgeDistanceBooleanGrid.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * This file is part of the Distant Horizons mod - * licensed under the GNU LGPL v3 License. - * - * Copyright (C) 2020-2023 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 . - */ - -package com.seibel.distanthorizons.core.util.gridList; - -import com.seibel.distanthorizons.core.pos.Pos2D; -import com.seibel.distanthorizons.core.util.objects.BoolType; - -import java.util.Iterator; -import java.util.function.IntPredicate; - -public class EdgeDistanceBooleanGrid extends PosArrayGridList -{ - ArrayGridList edgeCache = null; - - public EdgeDistanceBooleanGrid(Iterator posIter, int offsetX, int offsetY, int gridSize) - { - super(gridSize, offsetX, offsetY); - while (posIter.hasNext()) - { - Pos2D p = posIter.next(); - this.set(p, BoolType.TRUE); - } - } - - // Return false if it is indeed updated - private static boolean updatePos(ArrayGridList grid, int ox, int oy) - { - if (grid.get(ox, oy) < 0) return true; - if (ox == 0 || oy == 0 || ox == grid.gridSize - 1 || oy == grid.gridSize - 1) - { - return true; - } - - int v = grid.get(ox, oy); - if ( - grid.get(ox, oy + 1) < v || - grid.get(ox, oy - 1) < v || - grid.get(ox + 1, oy) < v || - grid.get(ox - 1, oy) < v - ) - { - return true; - } - else - { - grid.set(ox, oy, v + 1); - return false; - } - } - - //FIXME: This is slow and expensive. Use queue to make this skip recheck done pos - private void computeEdgeCache() - { - if (edgeCache != null) return; - - edgeCache = new ArrayGridList(gridSize, (ox, oy) -> { - BoolType b = get(ox + getOffsetX(), oy + getOffsetY()); - return b == null ? -1 : 0; - }); - - final boolean[] isDone = {false}; - while (!isDone[0]) - { - isDone[0] = true; - edgeCache.forEachPos((ox, oy) -> { - isDone[0] &= updatePos(edgeCache, ox, oy); - }); - } - } - - // 0 means right on the edge, while 1 means 1 ceil away. Uses Manhattan Distance - public > void flagAllWithDistance(T list, IntPredicate predicate) - { - computeEdgeCache(); - edgeCache.forEachPos((ox, oy) -> { - int v = edgeCache.get(ox, oy); - if (v < 0 || !predicate.test(v)) return; - list.set(ox + getOffsetX(), oy + getOffsetY(), BoolType.TRUE); - }); - } - - -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/ChunkLightStorage.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/ChunkLightStorage.java index 73ced41be..a17c8b06d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/ChunkLightStorage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/ChunkLightStorage.java @@ -141,6 +141,25 @@ public class ChunkLightStorage lightSection.set(x, y, z, lightLevel); } + public void clear() + { + if (this.lightSections != null) + { + for (int i = 0; i < this.lightSections.length; i++) + { + LightSection section = this.lightSections[i]; + if (section != null) + { + section.constantValue = LodUtil.MIN_MC_LIGHT; + if (section.data != null) + { + Arrays.fill(section.data, 0L); + } + } + } + } + } + //================// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java index 2ed6ad9ce..b059ed445 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java @@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.pos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; import com.seibel.distanthorizons.core.util.LodUtil; @@ -79,9 +80,11 @@ public interface IChunkWrapper extends IBindable int getDhSkyLight(int relX, int relY, int relZ); void setDhSkyLight(int relX, int relY, int relZ, int lightValue); + void clearDhSkyLighting(); int getDhBlockLight(int relX, int relY, int relZ); void setDhBlockLight(int relX, int relY, int relZ, int lightValue); + void clearDhBlockLighting(); int getBlockLight(int relX, int relY, int relZ); int getSkyLight(int relX, int relY, int relZ); @@ -150,29 +153,77 @@ public interface IChunkWrapper extends IBindable * This is generally done in cases where MC's lighting is correct now, but may not be later (like when a chunk is unloading). * * @throws IllegalStateException if the chunk's lighting isn't valid. This is done to prevent accidentally baking broken lighting. + * @return true if the chunk's lighting was successfully populated, false otherwise */ - default void bakeDhLightingUsingMcLightingEngine() throws IllegalStateException + @Deprecated + default boolean bakeDhLightingUsingMcLightingEngine(ILevelWrapper levelWrapper) throws IllegalStateException { if (!this.isLightCorrect()) { - throw new IllegalStateException("Unable to bake lighting for for chunk [" + this.getChunkPos() + "], Minecraft lighting not valid."); + return false; } - // get the lighting for every relative block pos + //=======================// + // get lighting for each // + // relative block pos // + //=======================// + + boolean lightingFound = false; + // if the level doesn't have sky lights, then this check can be ignored + // since all sky light values will be 0 anyway + boolean skyLightingFound = !levelWrapper.hasSkyLight(); + for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++) { for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++) { for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++) { - this.setDhSkyLight(relX, y, relZ, this.getSkyLight(relX, y, relZ)); - this.setDhBlockLight(relX, y, relZ, this.getBlockLight(relX, y, relZ)); + int skyLight = this.getSkyLight(relX, y, relZ); + this.setDhSkyLight(relX, y, relZ, skyLight); + int blockLight = this.getBlockLight(relX, y, relZ); + this.setDhBlockLight(relX, y, relZ, blockLight); + + // MC defaults to max sky light and no block light, including underground blocks. + // If any position has something different then those default values, it's likely that the + // lighting was properly populated for at least part of the chunk + if (!lightingFound && + (skyLight != LodUtil.MAX_MC_LIGHT || blockLight != LodUtil.MIN_MC_LIGHT)) + { + lightingFound = true; + } + + if (!skyLightingFound + && skyLight != LodUtil.MIN_MC_LIGHT) + { + skyLightingFound = true; + } } } } + + + //=================// + // validate result // + //=================// + + // if no lighting was found or the sky is always black, the lighting is likely broken + if (!lightingFound || !skyLightingFound + // if lighting is no longer correct or doesn't match the saved values + // its very likely it broke halfway through and will need regenerating + || !this.isLightCorrect() + || this.getSkyLight(0, 0, 0) != this.getDhSkyLight(0,0,0) + || this.getBlockLight(0, 0, 0) != this.getDhBlockLight(0,0,0)) + { + return false; + } + + + // lighting is valid this.setIsDhLightCorrect(true); this.setUseDhLighting(true); + return true; } @@ -229,18 +280,42 @@ public interface IChunkWrapper extends IBindable int hash = 31; int primeBlockMultiplier = 227; int primeBiomeMultiplier = 701; + int primeHeightMultiplier = 137; - int minBuildHeight = this.getMinBuildHeight(); - int maxBuildHeight = this.getMaxBuildHeight(); + int minBuildHeight = this.getMaxNonEmptyHeight(); + int maxBuildHeight = this.getMinNonEmptyHeight(); + + // most blocks (only some blocks are sampled since checking every block is a very slow operation) + for (int x = 0; x < LodUtil.CHUNK_WIDTH; x+=2) + { + for (int z = 0; z < LodUtil.CHUNK_WIDTH; z+=2) + { + for (int y = minBuildHeight; y < maxBuildHeight; y+=8) + { + hash = (hash * primeBlockMultiplier) + this.getBlockState(x, y, z).hashCode(); + hash = (hash * primeBiomeMultiplier) + this.getBiome(x, y, z).hashCode(); + hash = (hash * primeHeightMultiplier) + y; + } + } + } + + // surface (this should cover most cases for when users modify chunks) for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++) { for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++) { - for (int y = minBuildHeight; y < maxBuildHeight; y++) + int lightBlockingY = this.getLightBlockingHeightMapValue(x, z); + hash = (hash * primeBlockMultiplier) + this.getBlockState(x, lightBlockingY, z).hashCode(); + hash = (hash * primeBiomeMultiplier) + this.getBiome(x, lightBlockingY, z).hashCode(); + hash = (hash * primeHeightMultiplier) + lightBlockingY; + + int solidY = this.getSolidHeightMapValue(x, z); + if (solidY != lightBlockingY) { - hash = (hash * primeBlockMultiplier) + this.getBlockState(x, y, z).hashCode(); - hash = (hash * primeBiomeMultiplier) + this.getBiome(x, y, z).hashCode(); + hash = (hash * primeBlockMultiplier) + this.getBlockState(x, solidY, z).hashCode(); + hash = (hash * primeBiomeMultiplier) + this.getBiome(x, solidY, z).hashCode(); + hash = (hash * primeHeightMultiplier) + solidY; } } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java index 2fc860772..538f8cc0f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java @@ -48,18 +48,10 @@ public interface IMinecraftRenderWrapper extends IBindable { Vec3f getLookAtVector(); - DhBlockPos getCameraBlockPosition(); - boolean playerHasBlindingEffect(); Vec3d getCameraExactPosition(); - Mat4f getWorldViewMatrix(); - - Mat4f getDefaultProjectionMatrix(float partialTicks); - - double getGamma(); - Color getFogColor(float partialTicks); default Color getSpecialFogColor(float partialTicks) { return getFogColor(partialTicks); } @@ -90,63 +82,6 @@ public interface IMinecraftRenderWrapper extends IBindable */ void clearTargetFrameBuffer(); - /** - * This method returns the ChunkPos of all chunks that Minecraft - * is going to render this frame. - *
- * If not implemented this calls {@link #getMaximumRenderedChunks()}. - */ - default HashSet getVanillaRenderedChunks() - { - // FIXME: Is this actually required? Does it make a differance if it exists or not? - ISodiumAccessor sodium = ModAccessorInjector.INSTANCE.get(ISodiumAccessor.class); - return sodium == null ? getMaximumRenderedChunks() : sodium.getNormalRenderedChunks(); - } - - static boolean correctedCheckRadius(int dx, int dz, int radius2Mul4) - { - dx = dx * 2;// + (dx < 0 ? -1 : 1); - dz = dz * 2;// + (dz < 0 ? -1 : 1); - return (dx * dx + dz * dz <= radius2Mul4); - } - - /** - * Doesn't need to be implemented.
- * Returns every chunk position within the vanilla render distance. - */ - default HashSet getMaximumRenderedChunks() - { - IMinecraftClientWrapper mcWrapper = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - IWrapperFactory factory = SingletonInjector.INSTANCE.get(IWrapperFactory.class); - IVersionConstants versionConstants = SingletonInjector.INSTANCE.get(IVersionConstants.class); - IMinecraftClientWrapper minecraft = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - ILevelWrapper clientWorld = minecraft.getWrappedClientLevel(); - - int chunkDist = this.getRenderDistance() + 1; // For some reason having '+1' is actually closer to real value - - DhChunkPos centerChunkPos = mcWrapper.getPlayerChunkPos(); - int centerChunkX = centerChunkPos.x; - int centerChunkZ = centerChunkPos.z; - int chunkDist2Mul4 = chunkDist * chunkDist * 4; - - // add every position within render distance - HashSet renderedPos = new HashSet(); - for (int deltaChunkX = -chunkDist; deltaChunkX <= chunkDist; deltaChunkX++) - { - for (int deltaChunkZ = -chunkDist; deltaChunkZ <= chunkDist; deltaChunkZ++) - { - if (!versionConstants.isVanillaRenderedChunkSquare() && - !correctedCheckRadius(deltaChunkX, deltaChunkZ, chunkDist2Mul4)) - { - continue; - } - if (!clientWorld.hasChunkLoaded(centerChunkX + deltaChunkX, centerChunkZ + deltaChunkZ)) continue; - renderedPos.add(new DhChunkPos(centerChunkX + deltaChunkX, centerChunkZ + deltaChunkZ)); - } - } - return renderedPos; - } - /** Can return null if the given level hasn't had a light map assigned to it */ @Nullable ILightMapWrapper getLightmapWrapper(ILevelWrapper level); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java index 82ca5efc0..c7eda6885 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java @@ -25,8 +25,6 @@ import java.util.HashSet; public interface ISodiumAccessor extends IModAccessor { - HashSet getNormalRenderedChunks(); - /** A temporary overwrite for a config in sodium 0.5 to fix their terrain from showing, will be removed once a proper fix is added */ void setFogOcclusion(boolean b); // FIXME 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 9abd4d402..26e2565b1 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -587,6 +587,8 @@ "Show Low Memory Warning", "distanthorizons.config.client.advanced.logging.showReplayWarningOnStartup": "Show Replay Warning", + "distanthorizons.config.client.advanced.logging.showModCompatibilityWarningsOnStartup": + "Show Mod Compatibility Warnings", diff --git a/core/src/test/java/testItems/lightingEngine/LightingTestChunkWrapper.java b/core/src/test/java/testItems/lightingEngine/LightingTestChunkWrapper.java index 8317a6509..142113859 100644 --- a/core/src/test/java/testItems/lightingEngine/LightingTestChunkWrapper.java +++ b/core/src/test/java/testItems/lightingEngine/LightingTestChunkWrapper.java @@ -350,6 +350,8 @@ public class LightingTestChunkWrapper implements IChunkWrapper } return this.blockLightStorage; } + @Override + public void clearDhBlockLighting() { throw new UnsupportedOperationException("Not implemented"); } @Override @@ -364,6 +366,8 @@ public class LightingTestChunkWrapper implements IChunkWrapper this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); this.getSkyLightStorage().set(relX, y, relZ, lightValue); } + @Override + public void clearDhSkyLighting() { throw new UnsupportedOperationException("Not implemented"); } private ChunkLightStorage getSkyLightStorage() {