From df98b960821fd66f1cbae1047965641ea55dda52 Mon Sep 17 00:00:00 2001 From: contaria Date: Tue, 12 Nov 2024 12:09:05 +0100 Subject: [PATCH 1/3] optimization: set capacity of vanilla visibleChunks list to 0 Vanilla initializes its visibleChunks list with a capacity of 69696, with SeedQueues WorldRenderers for the wall screen that can add up to multiple megabytes of completely unused memory --- .../mixin/features/chunk_rendering/MixinWorldRenderer.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/me/jellysquid/mods/sodium/mixin/features/chunk_rendering/MixinWorldRenderer.java b/src/main/java/me/jellysquid/mods/sodium/mixin/features/chunk_rendering/MixinWorldRenderer.java index e579ae44..e73bd3b8 100644 --- a/src/main/java/me/jellysquid/mods/sodium/mixin/features/chunk_rendering/MixinWorldRenderer.java +++ b/src/main/java/me/jellysquid/mods/sodium/mixin/features/chunk_rendering/MixinWorldRenderer.java @@ -16,6 +16,7 @@ import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -38,6 +39,12 @@ public SodiumWorldRenderer getSodiumWorldRenderer() { return renderer; } + @ModifyArg(method = "", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/objects/ObjectArrayList;(I)V")) + private int nullifyVisibleChunksList(int capacity) { + // Sodium doesn't use this list, so we prevent the initial capacity of 69696 to be allocated + return 0; + } + @Redirect(method = "reload", at = @At(value = "FIELD", target = "Lnet/minecraft/client/options/GameOptions;viewDistance:I", ordinal = 1)) private int nullifyBuiltChunkStorage(GameOptions options) { // Do not allow any resources to be allocated From f4a01e565124a2dad3c76cb69e1d195c6758ec9c Mon Sep 17 00:00:00 2001 From: jojoe77777 Date: Thu, 14 Nov 2024 05:30:45 -0500 Subject: [PATCH 2/3] Rename renderThreads to chunkUpdateThreads and moved category Set auto default value for render thread count Added render threads option --- .../sodium/client/gui/SodiumGameOptions.java | 2 ++ .../render/chunk/compile/ChunkBuilder.java | 29 +++++++++++++------ .../assets/sodiummac/lang/en_us.json | 3 ++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java b/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java index bc5c44ad..478b4223 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java @@ -32,6 +32,8 @@ public class SodiumGameOptions implements SpeedrunConfig { public final SpeedrunSettings speedrun = new SpeedrunSettings(); public static class AdvancedSettings implements SpeedrunConfigStorage { + @Config.Numbers.Whole.Bounds(min = 0, max = 32) + public int chunkUpdateThreads = 0; public ChunkRendererBackendOption chunkRendererBackend = ChunkRendererBackendOption.BEST; public boolean useChunkFaceCulling = true; public boolean useCompactVertexFormat = true; diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder.java index 069ebce5..80593e05 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder.java @@ -1,5 +1,6 @@ package me.jellysquid.mods.sodium.client.render.chunk.compile; +import me.jellysquid.mods.sodium.client.SodiumClientMod; import me.jellysquid.mods.sodium.client.gl.attribute.GlVertexFormat; import me.jellysquid.mods.sodium.client.render.chunk.ChunkGraphicsState; import me.jellysquid.mods.sodium.client.render.chunk.ChunkRenderBackend; @@ -17,6 +18,7 @@ import me.jellysquid.mods.sodium.common.util.pool.ObjectPool; import net.minecraft.client.MinecraftClient; import net.minecraft.client.util.math.Vector3d; +import net.minecraft.util.math.MathHelper; import net.minecraft.client.world.ClientWorld; import net.minecraft.util.math.ChunkSectionPos; import net.minecraft.world.World; @@ -61,10 +63,27 @@ public class ChunkBuilder { public ChunkBuilder(GlVertexFormat format, ChunkRenderBackend backend) { this.format = format; this.backend = backend; - this.limitThreads = getOptimalThreadCount(); + this.limitThreads = getThreadCount(); this.pool = new ObjectPool<>(this.getSchedulingBudget(), WorldSlice::new); } + /** + * Returns the "optimal" number of threads to be used for chunk build tasks. This will always return at least one + * thread. + */ + private static int getOptimalThreadCount() { + return MathHelper.clamp(Math.max(getMaxThreadCount() / 3, getMaxThreadCount() - 6), 1, 10); + } + + private static int getThreadCount() { + int requested = SodiumClientMod.options().advanced.chunkUpdateThreads; + return requested == 0 ? getOptimalThreadCount() : Math.min(requested, getMaxThreadCount()); + } + + private static int getMaxThreadCount() { + return Runtime.getRuntime().availableProcessors(); + } + /** * Returns the remaining number of build tasks which should be scheduled this frame. If an attempt is made to * spawn more tasks than the budget allows, it will block until resources become available. @@ -221,14 +240,6 @@ public void init(ClientWorld world, BlockRenderPassManager renderPassManager) { this.startWorkers(); } - /** - * Returns the "optimal" number of threads to be used for chunk build tasks. This is always at least one thread, - * but can be up to the number of available processor threads on the system. - */ - private static int getOptimalThreadCount() { - return Math.max(1, Runtime.getRuntime().availableProcessors()); - } - /** * Creates a {@link WorldSlice} around the given chunk section. If the chunk section is empty, null is returned. * @param pos The position of the chunk section diff --git a/src/main/resources/assets/sodiummac/lang/en_us.json b/src/main/resources/assets/sodiummac/lang/en_us.json index 407c5cae..7c27904c 100644 --- a/src/main/resources/assets/sodiummac/lang/en_us.json +++ b/src/main/resources/assets/sodiummac/lang/en_us.json @@ -4,6 +4,9 @@ "speedrunapi.config.sodiummac.category.speedrun": "Speedrun", "speedrunapi.config.sodiummac.option.quality:enableVignette": "Vignette", "speedrunapi.config.sodiummac.option.quality:enableVignette.description": "If enabled, a vignette effect will be rendered on the player's view. This is very unlikely to make a difference to frame rates unless you are fill-rate limited.", + "speedrunapi.config.sodium.option.advanced:chunkUpdateThreads": "Chunk Update Threads", + "speedrunapi.config.sodium.option.advanced:chunkUpdateThreads.description": "How many chunk update threads to create (lower = less F3+F lag)", + "speedrunapi.config.sodium.option.advanced:chunkUpdateThreads.value.0": "Auto", "speedrunapi.config.sodiummac.option.advanced:chunkRendererBackend": "Chunk Renderer", "speedrunapi.config.sodiummac.option.advanced:chunkRendererBackend.description": "Modern versions of OpenGL provide features which can be used to greatly reduce driver overhead when rendering chunks. You should use the latest feature set allowed by Sodium for optimal performance. If you're experiencing chunk rendering issues or driver crashes, try using the older (and possibly more stable) feature sets.", "speedrunapi.config.sodiummac.option.advanced:chunkRendererBackend.value.GL43": "Multidraw (GL 4.3)", From 922a97a81d087ff8afb58d80a95c69599d849713 Mon Sep 17 00:00:00 2001 From: DesktopFolder Date: Wed, 27 Nov 2024 22:24:16 -0500 Subject: [PATCH 3/3] Review: Use milliseconds placeholder; reformat code/comments Improve scale of slow creation thread interval. Reapply full @tildejustin changes. Review - simplify / better naming. Finish up gradual thread creation system. Add configurable gradual thread creation. System-time based. --- .../sodium/client/gui/SodiumGameOptions.java | 12 +- .../client/render/SodiumWorldRenderer.java | 4 + .../render/chunk/ChunkRenderManager.java | 2 + .../render/chunk/compile/ChunkBuilder.java | 110 ++++++++++++++---- .../assets/sodiummac/lang/en_us.json | 18 ++- 5 files changed, 117 insertions(+), 29 deletions(-) diff --git a/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java b/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java index 478b4223..981ea088 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java @@ -32,8 +32,16 @@ public class SodiumGameOptions implements SpeedrunConfig { public final SpeedrunSettings speedrun = new SpeedrunSettings(); public static class AdvancedSettings implements SpeedrunConfigStorage { - @Config.Numbers.Whole.Bounds(min = 0, max = 32) - public int chunkUpdateThreads = 0; + @Config.Numbers.Whole.Bounds(min = 0, max = 64) + public int maxChunkThreads; + @Config.Numbers.Whole.Bounds(min = 0, max = 64) + public int targetChunkThreads; + @Config.Numbers.Whole.Bounds(min = 0, max = 64) + public int initialChunkThreads; + @Config.Numbers.Whole.Bounds(min = 1, max = 2000) + public int quickThreadCreationInterval = 100; + @Config.Numbers.Whole.Bounds(min = 1, max = 10000) + public int slowThreadCreationInterval = 1000; public ChunkRendererBackendOption chunkRendererBackend = ChunkRendererBackendOption.BEST; public boolean useChunkFaceCulling = true; public boolean useCompactVertexFormat = true; diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/SodiumWorldRenderer.java b/src/main/java/me/jellysquid/mods/sodium/client/render/SodiumWorldRenderer.java index 334b3929..cb3935ad 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/SodiumWorldRenderer.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/SodiumWorldRenderer.java @@ -426,4 +426,8 @@ public ChunkRenderBackend getChunkRenderer() { public boolean getUseEntityCulling() { return this.useEntityCulling; } + + public int getRenderDistance() { + return this.renderDistance; + } } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderManager.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderManager.java index 37eb65bc..12c3d757 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderManager.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderManager.java @@ -462,6 +462,8 @@ public void updateChunks() { if (!futures.isEmpty()) { this.backend.upload(new FutureDequeDrain<>(futures)); } + + this.builder.createMoreThreads(); } public void markDirty() { diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder.java index 80593e05..c98e94c7 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuilder.java @@ -2,6 +2,7 @@ import me.jellysquid.mods.sodium.client.SodiumClientMod; import me.jellysquid.mods.sodium.client.gl.attribute.GlVertexFormat; +import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer; import me.jellysquid.mods.sodium.client.render.chunk.ChunkGraphicsState; import me.jellysquid.mods.sodium.client.render.chunk.ChunkRenderBackend; import me.jellysquid.mods.sodium.client.render.chunk.ChunkRenderContainer; @@ -56,40 +57,90 @@ public class ChunkBuilder { private BiomeCacheManager biomeCacheManager; private BlockRenderPassManager renderPassManager; - private final int limitThreads; + // This is the initial number of threads we spin up upon ChunkBuilder creation. + // Default: 2-4 depending on render distance. *Always* less than hardLimitThreads. + private final int initialThreads; + // This is the number of threads we want to 'quickly' get up to. This is calculated + // by getOptimalThreadCount, but can also be configured by the user. Always less than hardLimitThreads. + private final int targetThreads; + // This is the number of threads we are allowed to create in total. + // This is defaulted to targetThreads, but maxes out at 64. Testing would be required to determine what + // actual count is optimal for a specific user and use case. + private final int hardLimitThreads; + // This is the initial time when this builder is created. We use this to create more threads. + private long lastThreadAddition; + // This is the user-configurable time delta to create a new thread, up until targetThreads. + private final long quickThreadCreationInterval; + // This is the user-configurable time delta to create a new thread, up until hardLimitThreads. + private final long slowThreadCreationInterval; + private final GlVertexFormat format; private final ChunkRenderBackend backend; public ChunkBuilder(GlVertexFormat format, ChunkRenderBackend backend) { this.format = format; this.backend = backend; - this.limitThreads = getThreadCount(); + + // User-configurable options for chunk threads. + int desiredTargetThreads = SodiumClientMod.options().advanced.targetChunkThreads; + int desiredInitialThreads = SodiumClientMod.options().advanced.initialChunkThreads; + + // These are bounded by the options configuration. Both are measured in milliseconds. + this.quickThreadCreationInterval = SodiumClientMod.options().advanced.quickThreadCreationInterval; + this.slowThreadCreationInterval = SodiumClientMod.options().advanced.slowThreadCreationInterval; + + // Our hard limit of threads. Cap user config at 64, prefer desiredMaxThreads, otherwise use logical core count. + this.hardLimitThreads = getMaxThreadCount(); + // Our targeted number of threads. + this.targetThreads = Math.min(desiredTargetThreads == 0 ? getDefaultTargetThreads() : desiredTargetThreads, this.hardLimitThreads); + // Our initial threads. A bit of a silly calculation for this one. + this.initialThreads = Math.min(desiredInitialThreads == 0 ? getDefaultInitialThreads() : desiredInitialThreads, this.targetThreads); this.pool = new ObjectPool<>(this.getSchedulingBudget(), WorldSlice::new); } - /** - * Returns the "optimal" number of threads to be used for chunk build tasks. This will always return at least one - * thread. - */ - private static int getOptimalThreadCount() { - return MathHelper.clamp(Math.max(getMaxThreadCount() / 3, getMaxThreadCount() - 6), 1, 10); + private static int getDefaultTargetThreads() { + return MathHelper.clamp(Math.max(getLogicalCoreCount() / 3, getLogicalCoreCount() - 6), 1, 10); } - private static int getThreadCount() { - int requested = SodiumClientMod.options().advanced.chunkUpdateThreads; - return requested == 0 ? getOptimalThreadCount() : Math.min(requested, getMaxThreadCount()); + private static int getDefaultInitialThreads() { + return (SodiumWorldRenderer.getInstance().getRenderDistance() / 10) + 2; } - private static int getMaxThreadCount() { + private static int getLogicalCoreCount() { return Runtime.getRuntime().availableProcessors(); } + // Split out this function so that SeedQueue can inject into this. + private static int getMaxThreadCount() { + int desiredMaxThreads = SodiumClientMod.options().advanced.maxChunkThreads; + return desiredMaxThreads == 0 ? getLogicalCoreCount() : desiredMaxThreads; + } + /** * Returns the remaining number of build tasks which should be scheduled this frame. If an attempt is made to * spawn more tasks than the budget allows, it will block until resources become available. */ public int getSchedulingBudget() { - return Math.max(0, (this.limitThreads * TASK_QUEUE_LIMIT_PER_WORKER) - this.buildQueue.size()); + return Math.max(0, (Math.max(this.threads.size(), this.targetThreads) * TASK_QUEUE_LIMIT_PER_WORKER) - this.buildQueue.size()); + } + + public void createWorker(MinecraftClient client) { + ChunkBuildBuffers buffers = new ChunkBuildBuffers(this.format, this.renderPassManager); + ChunkRenderContext pipeline = new ChunkRenderContext(client); + + WorkerRunnable worker = new WorkerRunnable(buffers, pipeline); + + Thread thread = new Thread(worker, "Chunk Render Task Executor #" + this.threads.size() + 1); + thread.setPriority(Math.max(0, Thread.NORM_PRIORITY - 2)); + thread.start(); + + this.threads.add(thread); + + // Helper debug message. Prints at most once per reload, so shouldn't noticeably increase log spam. + if (this.threads.size() == this.hardLimitThreads) { + LOGGER.info("Reached maximum Sodium builder threads of {}", this.hardLimitThreads); + } + this.lastThreadAddition = System.currentTimeMillis(); } /** @@ -106,21 +157,32 @@ public void startWorkers() { } MinecraftClient client = MinecraftClient.getInstance(); + for (int i = 0; i < this.initialThreads; i++) { + this.createWorker(client); + } - for (int i = 0; i < this.limitThreads; i++) { - ChunkBuildBuffers buffers = new ChunkBuildBuffers(this.format, this.renderPassManager); - ChunkRenderContext pipeline = new ChunkRenderContext(client); - - WorkerRunnable worker = new WorkerRunnable(buffers, pipeline); - - Thread thread = new Thread(worker, "Chunk Render Task Executor #" + i); - thread.setPriority(Math.max(0, Thread.NORM_PRIORITY - 2)); - thread.start(); + LOGGER.info("Started {} worker threads", this.threads.size()); + } - this.threads.add(thread); + /** + * Spawns workers if we have thread space. + */ + public void createMoreThreads() { + if (this.threads.size() >= this.hardLimitThreads) { + return; } - LOGGER.info("Started {} worker threads", this.threads.size()); + long timeDelta = System.currentTimeMillis() - this.lastThreadAddition; + if (this.threads.size() < this.targetThreads) { + // Check if enough time has elapsed for us to create a target thread. + if (timeDelta > this.quickThreadCreationInterval) { + this.createWorker(MinecraftClient.getInstance()); + } + } + // Check if enough time has elapsed for us to create a target thread. + else if (timeDelta > this.slowThreadCreationInterval) { + this.createWorker(MinecraftClient.getInstance()); + } } /** diff --git a/src/main/resources/assets/sodiummac/lang/en_us.json b/src/main/resources/assets/sodiummac/lang/en_us.json index 7c27904c..9a7928ab 100644 --- a/src/main/resources/assets/sodiummac/lang/en_us.json +++ b/src/main/resources/assets/sodiummac/lang/en_us.json @@ -4,9 +4,21 @@ "speedrunapi.config.sodiummac.category.speedrun": "Speedrun", "speedrunapi.config.sodiummac.option.quality:enableVignette": "Vignette", "speedrunapi.config.sodiummac.option.quality:enableVignette.description": "If enabled, a vignette effect will be rendered on the player's view. This is very unlikely to make a difference to frame rates unless you are fill-rate limited.", - "speedrunapi.config.sodium.option.advanced:chunkUpdateThreads": "Chunk Update Threads", - "speedrunapi.config.sodium.option.advanced:chunkUpdateThreads.description": "How many chunk update threads to create (lower = less F3+F lag)", - "speedrunapi.config.sodium.option.advanced:chunkUpdateThreads.value.0": "Auto", + "speedrunapi.config.sodium.option.advanced:initialChunkThreads": "Initial Chunk Threads", + "speedrunapi.config.sodium.option.advanced:initialChunkThreads.description": "How many chunk building threads to create instantly when reloading (lower = less F3 + F lag). Cannot be more than Target Chunk Threads.", + "speedrunapi.config.sodium.option.advanced:initialChunkThreads.value.0": "Auto", + "speedrunapi.config.sodium.option.advanced:targetChunkThreads": "Target Chunk Threads", + "speedrunapi.config.sodium.option.advanced:targetChunkThreads.description": "How many chunk building threads to create quickly after reloading (ideal: Number of hardware threads). Cannot be more than Maximum Chunk Threads.", + "speedrunapi.config.sodium.option.advanced:targetChunkThreads.value.0": "Auto", + "speedrunapi.config.sodium.option.advanced:maxChunkThreads": "Maximum Chunk Threads", + "speedrunapi.config.sodium.option.advanced:maxChunkThreads.description": "How many chunk building threads to create in total (may help long-term chunk loading)", + "speedrunapi.config.sodium.option.advanced:maxChunkThreads.value.0": "Auto", + "speedrunapi.config.sodium.option.advanced:quickThreadCreationInterval": "Fast Thread Creation Interval", + "speedrunapi.config.sodium.option.advanced:quickThreadCreationInterval.description": "Interval between each chunk builder thread being created, up to targetChunkThreads.", + "speedrunapi.config.sodium.option.advanced:quickThreadCreationInterval.value": "%sms", + "speedrunapi.config.sodium.option.advanced:slowThreadCreationInterval": "Slow Thread Creation Interval", + "speedrunapi.config.sodium.option.advanced:slowThreadCreationInterval.description": "Interval between each chunk builder thread being created, up to maxChunkThreads.", + "speedrunapi.config.sodium.option.advanced:slowThreadCreationInterval.value": "%sms", "speedrunapi.config.sodiummac.option.advanced:chunkRendererBackend": "Chunk Renderer", "speedrunapi.config.sodiummac.option.advanced:chunkRendererBackend.description": "Modern versions of OpenGL provide features which can be used to greatly reduce driver overhead when rendering chunks. You should use the latest feature set allowed by Sodium for optimal performance. If you're experiencing chunk rendering issues or driver crashes, try using the older (and possibly more stable) feature sets.", "speedrunapi.config.sodiummac.option.advanced:chunkRendererBackend.value.GL43": "Multidraw (GL 4.3)",