Skip to content

Commit

Permalink
Merge pull request #41 from tildejustin/mac/1.16.1
Browse files Browse the repository at this point in the history
mac port of desktopfolder ChunkBuilder thread optimization and some other commits on 1.16.1
  • Loading branch information
tildejustin authored Dec 25, 2024
2 parents 94de2ac + 922a97a commit 7ae37a5
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +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 = 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,4 +426,8 @@ public ChunkRenderBackend<?> getChunkRenderer() {
public boolean getUseEntityCulling() {
return this.useEntityCulling;
}

public int getRenderDistance() {
return this.renderDistance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,8 @@ public void updateChunks() {
if (!futures.isEmpty()) {
this.backend.upload(new FutureDequeDrain<>(futures));
}

this.builder.createMoreThreads();
}

public void markDirty() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
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.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;
Expand All @@ -17,6 +19,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;
Expand Down Expand Up @@ -54,23 +57,90 @@ public class ChunkBuilder<T extends ChunkGraphicsState> {
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<T> backend;

public ChunkBuilder(GlVertexFormat<?> format, ChunkRenderBackend<T> backend) {
this.format = format;
this.backend = backend;
this.limitThreads = getOptimalThreadCount();

// 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);
}

private static int getDefaultTargetThreads() {
return MathHelper.clamp(Math.max(getLogicalCoreCount() / 3, getLogicalCoreCount() - 6), 1, 10);
}

private static int getDefaultInitialThreads() {
return (SodiumWorldRenderer.getInstance().getRenderDistance() / 10) + 2;
}

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();
}

/**
Expand All @@ -87,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());
}
}

/**
Expand Down Expand Up @@ -221,14 +302,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -38,6 +39,12 @@ public SodiumWorldRenderer getSodiumWorldRenderer() {
return renderer;
}

@ModifyArg(method = "<init>", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/objects/ObjectArrayList;<init>(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
Expand Down
15 changes: 15 additions & 0 deletions src/main/resources/assets/sodiummac/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +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: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)",
Expand Down

0 comments on commit 7ae37a5

Please sign in to comment.