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.
This commit is contained in:
Moritz Zwerger 2025-02-08 18:22:11 +01:00
parent f20f2b5e0b
commit 055b015390
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
5 changed files with 43 additions and 12 deletions

View File

@ -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)
}

View File

@ -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)

View File

@ -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<ChunkMeshes> = 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
}
}

View File

@ -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)

View File

@ -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