From 055b01539038e93a62086890b61929ed2d4bf488 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Sat, 8 Feb 2025 18:22:11 +0100 Subject: [PATCH] limit time for gpu chunk transfers "Smooth FPS" Adds an option to the config, and limits to time the chunk renderer has to transfer to about 3ms. This works good and feels a lot smoother now. --- .../rendering/performance/PerformanceC.kt | 9 ++++++++- .../gui/rendering/chunk/ChunkRenderer.kt | 3 +++ .../chunk/queue/loading/MeshLoadingQueue.kt | 16 ++++++++++++---- .../chunk/queue/loading/MeshUnloadingQueue.kt | 10 +++++++--- .../rendering/chunk/util/ChunkRendererUtil.kt | 17 +++++++++++++---- 5 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/main/java/de/bixilon/minosoft/config/profile/profiles/rendering/performance/PerformanceC.kt b/src/main/java/de/bixilon/minosoft/config/profile/profiles/rendering/performance/PerformanceC.kt index 7885ab374..b93c3e89b 100644 --- a/src/main/java/de/bixilon/minosoft/config/profile/profiles/rendering/performance/PerformanceC.kt +++ b/src/main/java/de/bixilon/minosoft/config/profile/profiles/rendering/performance/PerformanceC.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2020-2023 Moritz Zwerger + * Copyright (C) 2020-2025 Moritz Zwerger * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * @@ -36,4 +36,11 @@ class PerformanceC(profile: RenderingProfile) { * Sleeps 100 ms if the rendering window is not in focus anymore */ var slowRendering by BooleanDelegate(profile, true) + + /** + * Limits the time of chunk transfers to the gpu. + * Increases time until far chunks are loaded. + * If disabled, lag spikes can occur when crossing chunk borders + */ + var limitChunkTransferTime by BooleanDelegate(profile, true) } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt index 86aea6efc..806aad53d 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt @@ -90,6 +90,8 @@ class ChunkRenderer( var cameraChunkPosition = Vec2i.EMPTY var cameraSectionHeight = 0 + var limitChunkTransferTime = true + override fun registerLayers() { layers.register(OpaqueLayer, shader, this::drawBlocksOpaque) { visible.opaque.isEmpty() } layers.register(TranslucentLayer, shader, this::drawBlocksTranslucent) { visible.translucent.isEmpty() } @@ -128,6 +130,7 @@ class ChunkRenderer( val rendering = session.profiles.rendering rendering.performance::fastBedrock.observe(this) { clearChunkCache() } rendering.light::ambientOcclusion.observe(this) { clearChunkCache() } + rendering.performance::limitChunkTransferTime.observe(this) { this.limitChunkTransferTime = it } profile::viewDistance.observe(this) { viewDistance -> val distance = maxOf(viewDistance, profile.simulationDistance) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshLoadingQueue.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshLoadingQueue.kt index 824a11d35..b3b29023a 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshLoadingQueue.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshLoadingQueue.kt @@ -15,13 +15,13 @@ package de.bixilon.minosoft.gui.rendering.chunk.queue.loading import de.bixilon.kutil.cast.CastUtil.unsafeNull import de.bixilon.kutil.concurrent.lock.Lock -import de.bixilon.kutil.time.TimeUtil import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.gui.rendering.chunk.ChunkRenderer import de.bixilon.minosoft.gui.rendering.chunk.mesh.ChunkMeshes import de.bixilon.minosoft.gui.rendering.chunk.queue.QueuePosition import de.bixilon.minosoft.gui.rendering.chunk.util.ChunkRendererUtil.maxBusyTime import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import kotlin.time.TimeSource class MeshLoadingQueue( private val renderer: ChunkRenderer, @@ -41,14 +41,18 @@ class MeshLoadingQueue( } var count = 0 - val start = TimeUtil.millis() - val maxTime = renderer.maxBusyTime // If the player is still, then we can load more chunks (to not cause lags) + val start = TimeSource.Monotonic.markNow() + val maxTime = renderer.maxBusyTime var meshes: Int2ObjectOpenHashMap = unsafeNull() var position: ChunkPosition? = null renderer.loaded.lock() - while (this.meshes.isNotEmpty() && (TimeUtil.millis() - start < maxTime)) { + var index = 0 + while (true) { + if (this.meshes.isEmpty()) break + if (index++ % BATCH_SIZE == 0 && TimeSource.Monotonic.markNow() - start >= maxTime) break + val mesh = this.meshes.removeAt(0) this.positions -= QueuePosition(mesh) @@ -152,4 +156,8 @@ class MeshLoadingQueue( this.lock.unlock() renderer.lock.release() } + + companion object { + const val BATCH_SIZE = 5 + } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshUnloadingQueue.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshUnloadingQueue.kt index d45c5f333..ef3ef4383 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshUnloadingQueue.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/queue/loading/MeshUnloadingQueue.kt @@ -14,11 +14,11 @@ package de.bixilon.minosoft.gui.rendering.chunk.queue.loading import de.bixilon.kutil.concurrent.lock.Lock -import de.bixilon.kutil.time.TimeUtil import de.bixilon.minosoft.gui.rendering.chunk.ChunkRenderer import de.bixilon.minosoft.gui.rendering.chunk.mesh.ChunkMeshes import de.bixilon.minosoft.gui.rendering.chunk.queue.QueuePosition import de.bixilon.minosoft.gui.rendering.chunk.util.ChunkRendererUtil.maxBusyTime +import kotlin.time.TimeSource class MeshUnloadingQueue( private val renderer: ChunkRenderer, @@ -33,10 +33,14 @@ class MeshUnloadingQueue( return } - val time = TimeUtil.millis() + val start = TimeSource.Monotonic.markNow() val maxTime = renderer.maxBusyTime - while (meshes.isNotEmpty() && (TimeUtil.millis() - time < maxTime)) { + var index = 0 + while (true) { + if (meshes.isEmpty()) break + if (index++ % MeshLoadingQueue.BATCH_SIZE == 0 && TimeSource.Monotonic.markNow() - start >= maxTime) break + val mesh = meshes.removeAt(0) this.positions -= QueuePosition(mesh) renderer.visible.removeMesh(mesh) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/util/ChunkRendererUtil.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/util/ChunkRendererUtil.kt index 5cd41d5a3..9091136a3 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/util/ChunkRendererUtil.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/util/ChunkRendererUtil.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2020-2024 Moritz Zwerger + * Copyright (C) 2020-2025 Moritz Zwerger * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * @@ -17,14 +17,23 @@ import de.bixilon.minosoft.data.world.chunk.ChunkSection import de.bixilon.minosoft.gui.rendering.chunk.ChunkRenderer import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.isEmpty import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds object ChunkRendererUtil { - const val STILL_LOADING_TIME = 50L - const val MOVING_LOADING_TIME = 20L + val STILL_LOADING_TIME = 30.milliseconds + val MOVING_LOADING_TIME = 3.milliseconds // If the player is still, then we can load more chunks (to not cause lags) - val ChunkRenderer.maxBusyTime: Long get() = if (session.player.physics.velocity.isEmpty()) STILL_LOADING_TIME else MOVING_LOADING_TIME // TODO: get of camera + val ChunkRenderer.maxBusyTime: Duration + get() { + if (!limitChunkTransferTime) return Duration.INFINITE + if (session.camera.entity.physics.velocity.isEmpty()) { + return STILL_LOADING_TIME + } + return MOVING_LOADING_TIME + } val ChunkSection.smallMesh: Boolean get() = blocks.count < ProtocolDefinition.SECTION_MAX_X * ProtocolDefinition.SECTION_MAX_Z