world renderer: rework preparing tasks

This commit is contained in:
Bixilon 2022-12-22 22:11:15 +01:00
parent 52cb9b4a57
commit 592aef6bda
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
6 changed files with 129 additions and 87 deletions

View File

@ -92,7 +92,7 @@ class DebugHUDElement(guiRenderer: GUIRenderer) : Element(guiRenderer), Layouted
layout += TextElement(guiRenderer, TextComponent(RunConfiguration.APPLICATION_NAME, ChatColors.RED))
layout += AutoTextElement(guiRenderer, 1) { "FPS §d${renderWindow.renderStats.smoothAvgFPS.rounded10}" }
renderWindow.renderer[WorldRenderer]?.apply {
layout += AutoTextElement(guiRenderer, 1) { "C v=$visibleSize, m=${loadedMeshesSize.format()}, cQ=${culledQueuedSize.format()}, q=${queueSize.format()}, pT=${preparingTasksSize.format()}/${maxPreparingTasks.format()}, l=${meshesToLoadSize.format()}/${maxMeshesToLoad.format()}, w=${connection.world.chunks.size.format()}" }
layout += AutoTextElement(guiRenderer, 1) { "C v=${visible.sizeString}, m=${loaded.size.format()}, cQ=${culledQueuedSize.format()}, q=${queue.size.format()}, pT=${queue.tasks.size.format()}/${queue.tasks.max.format()}, l=${loadingQueue.size.format()}/${queue.maxMeshesToLoad.format()}, w=${connection.world.chunks.size.format()}" }
}
layout += renderWindow.renderer[EntityRenderer]?.let {

View File

@ -54,9 +54,10 @@ import de.bixilon.minosoft.gui.rendering.world.preparer.FluidSectionPreparer
import de.bixilon.minosoft.gui.rendering.world.preparer.SolidSectionPreparer
import de.bixilon.minosoft.gui.rendering.world.preparer.cull.FluidCullSectionPreparer
import de.bixilon.minosoft.gui.rendering.world.preparer.cull.SolidCullSectionPreparer
import de.bixilon.minosoft.gui.rendering.world.queue.ChunkMeshingQueue
import de.bixilon.minosoft.gui.rendering.world.queue.MeshLoadingQueue
import de.bixilon.minosoft.gui.rendering.world.queue.MeshUnloadingQueue
import de.bixilon.minosoft.gui.rendering.world.queue.meshing.ChunkMeshingQueue
import de.bixilon.minosoft.gui.rendering.world.queue.meshing.tasks.MeshPrepareTask
import de.bixilon.minosoft.gui.rendering.world.shader.WorldShader
import de.bixilon.minosoft.gui.rendering.world.shader.WorldTextShader
import de.bixilon.minosoft.modding.event.events.RespawnEvent
@ -108,12 +109,12 @@ class WorldRenderer(
var cameraChunkPosition = Vec2i.EMPTY
var cameraSectionHeight = 0
val visibleSize: String get() = visible.sizeString
val loadedMeshesSize: Int get() = loaded.size
val culledQueuedSize: Int get() = culledQueue.size
val meshesToLoadSize: Int get() = loadingQueue.size
val queueSize: Int get() = queue.size
val preparingTasksSize: Int get() = queue.preparingTasks.size
@Deprecated("alias?", ReplaceWith("visible.sizeString")) val visibleSize: String get() = visible.sizeString
@Deprecated("alias?", ReplaceWith("loaded.size")) val loadedMeshesSize: Int get() = loaded.size
@Deprecated("alias?") val culledQueuedSize: Int get() = culledQueue.size
@Deprecated("alias?", ReplaceWith("loadingQueue.size")) val meshesToLoadSize: Int get() = loadingQueue.size
@Deprecated("alias?", ReplaceWith("queue.size")) val queueSize: Int get() = queue.size
@Deprecated("alias?", ReplaceWith("queue.tasks.size")) val preparingTasksSize: Int get() = queue.tasks.size
override fun init(latch: CountUpAndDownLatch) {
renderWindow.modelLoader.load(latch)
@ -262,8 +263,8 @@ class WorldRenderer(
queue.cleanup()
queue.tasks.cleanup()
loadingQueue.cleanup(false)
queue.interruptCleanup()
loaded.unlock()
@ -305,7 +306,7 @@ class WorldRenderer(
queue.interrupt()
queue.tasks.interruptAll()
unloadingQueue.lock()
loaded.clear(false)
@ -330,7 +331,7 @@ class WorldRenderer(
loaded.lock()
queue.interrupt(chunkPosition)
queue.tasks.interrupt(chunkPosition)
culledQueue.remove(chunkPosition)
@ -348,7 +349,7 @@ class WorldRenderer(
queue.unlock()
}
fun prepareItem(item: WorldQueueItem, task: SectionPrepareTask, runnable: ThreadPoolRunnable) {
fun prepareItem(item: WorldQueueItem, task: MeshPrepareTask, runnable: ThreadPoolRunnable) {
try {
val chunk = item.chunk ?: world[item.chunkPosition] ?: return
val section = chunk[item.sectionHeight] ?: return
@ -378,7 +379,7 @@ class WorldRenderer(
} finally {
task.runnable.interruptable = false
if (Thread.interrupted()) throw InterruptedException()
queue -= task
queue.tasks -= task
queue.work()
}
}
@ -398,11 +399,11 @@ class WorldRenderer(
}
}
queue.remove(item.chunkPosition, item.sectionHeight)
queue.remove(item)
loadingQueue.abort(item.chunkPosition, false)
queue.interrupt(item.chunkPosition, item.sectionHeight)
queue.tasks.interrupt(item.chunkPosition, item.sectionHeight)
loaded.unlock()
queue.unlock()

View File

@ -25,7 +25,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
class MeshLoadingQueue(
private val renderer: WorldRenderer,
) {
private val meshes: MutableList<WorldMesh> = mutableListOf() // prepared meshes, that can be loaded in the (next) frame
private val positions: MutableSet<QueuePosition> = HashSet()
private val lock = SimpleLock()

View File

@ -11,25 +11,24 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.world.queue
package de.bixilon.minosoft.gui.rendering.world.queue.meshing
import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
import de.bixilon.kutil.concurrent.pool.DefaultThreadPool
import de.bixilon.kutil.concurrent.pool.ThreadPool
import de.bixilon.kutil.concurrent.pool.ThreadPoolRunnable
import de.bixilon.minosoft.data.world.positions.ChunkPosition
import de.bixilon.minosoft.data.world.positions.SectionHeight
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.length2
import de.bixilon.minosoft.gui.rendering.world.SectionPrepareTask
import de.bixilon.minosoft.gui.rendering.world.WorldQueueItem
import de.bixilon.minosoft.gui.rendering.world.WorldRenderer
import de.bixilon.minosoft.gui.rendering.world.queue.meshing.tasks.MeshPrepareTask
import de.bixilon.minosoft.gui.rendering.world.queue.meshing.tasks.MeshPrepareTaskManager
import de.bixilon.minosoft.util.SystemInformation
class ChunkMeshingQueue(
private val renderer: WorldRenderer,
) {
val maxPreparingTasks = maxOf(DefaultThreadPool.threadCount - 2, 1)
val tasks = MeshPrepareTaskManager(renderer)
val maxMeshesToLoad = if (SystemInformation.RUNTIME.maxMemory() > 1_000_000_000) 150 else 80
@Volatile
@ -39,9 +38,6 @@ class ChunkMeshingQueue(
val lock = SimpleLock()
val preparingTasks: MutableSet<SectionPrepareTask> = mutableSetOf() // current running section preparing tasks
val preparingTasksLock = SimpleLock()
val size: Int get() = queue.size
@ -64,8 +60,8 @@ class ChunkMeshingQueue(
fun work() {
if (working) return // do not work twice
val size = preparingTasks.size
if (queue.isEmpty() || size >= maxPreparingTasks || renderer.loadingQueue.size >= maxMeshesToLoad) {
val size = tasks.size
if (queue.isEmpty() || size >= tasks.max || renderer.loadingQueue.size >= maxMeshesToLoad) {
return
}
working = true
@ -73,24 +69,20 @@ class ChunkMeshingQueue(
val items: MutableList<WorldQueueItem> = mutableListOf()
lock.lock()
for (i in 0 until maxPreparingTasks - size) {
for (i in 0 until tasks.max - size) {
if (queue.isEmpty()) {
break
}
val item = queue.removeFirst()
set.remove(item)
set -= item
items += item
}
lock.unlock()
for (item in items) {
val task = SectionPrepareTask(item.chunkPosition, item.sectionHeight, ThreadPoolRunnable(if (item.chunkPosition == renderer.cameraChunkPosition) ThreadPool.HIGH else ThreadPool.LOW, interruptable = true)) // Our own chunk is the most important one ToDo: Also make neighbour chunks important
task.runnable.runnable = Runnable {
renderer.prepareItem(item, task, task.runnable)
}
preparingTasksLock.lock()
preparingTasks += task
preparingTasksLock.unlock()
DefaultThreadPool += task.runnable
val runnable = ThreadPoolRunnable(if (item.chunkPosition == renderer.cameraChunkPosition) ThreadPool.HIGH else ThreadPool.LOW, interruptable = true) // Our own chunk is the most important one ToDo: Also make neighbour chunks important
val task = MeshPrepareTask(item.chunkPosition, item.sectionHeight, runnable)
task.runnable.runnable = Runnable { renderer.prepareItem(item, task, task.runnable) }
tasks += task
}
working = false
}
@ -128,66 +120,28 @@ class ChunkMeshingQueue(
this.lock.unlock()
}
fun interrupt(position: ChunkPosition) {
preparingTasksLock.acquire()
for (task in preparingTasks) {
if (task.chunkPosition == position) {
task.runnable.interrupt()
}
}
preparingTasksLock.release()
}
fun interrupt() {
preparingTasksLock.acquire()
for (task in preparingTasks) {
task.runnable.interrupt()
}
preparingTasksLock.release()
}
fun interrupt(position: ChunkPosition, height: SectionHeight) {
preparingTasksLock.acquire()
for (task in preparingTasks) {
if (task.chunkPosition == position && task.sectionHeight == height) {
task.runnable.interrupt()
}
}
preparingTasksLock.release()
}
fun interruptCleanup() {
preparingTasksLock.acquire()
for (task in preparingTasks) {
if (!renderer.visibilityGraph.isChunkVisible(task.chunkPosition)) {
task.runnable.interrupt()
}
}
preparingTasksLock.release()
}
fun clear() {
this.lock.lock()
this.queue.clear()
this.set.clear()
this.lock.unlock()
}
fun remove(task: SectionPrepareTask) {
preparingTasksLock.lock()
preparingTasks -= task
preparingTasksLock.unlock()
fun remove(item: WorldQueueItem) {
this.lock.lock()
if (this.set.remove(item)) {
this.queue -= item
}
operator fun minusAssign(task: SectionPrepareTask) = remove(task)
fun remove(position: ChunkPosition, height: SectionHeight) {
this.lock.unlock()
}
fun queue(item: WorldQueueItem) {
lock.lock()
if (set.remove(item)) {
queue.remove(item) // Prevent duplicated entries (to not prepare the same chunk twice (if it changed and was not prepared yet or ...)
queue -= item
}
if (item.chunkPosition == renderer.cameraChunkPosition) {
queue.add(0, item)

View File

@ -11,12 +11,12 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.world
package de.bixilon.minosoft.gui.rendering.world.queue.meshing.tasks
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.kutil.concurrent.pool.ThreadPoolRunnable
class SectionPrepareTask(
class MeshPrepareTask(
val chunkPosition: Vec2i,
val sectionHeight: Int,
val runnable: ThreadPoolRunnable,

View File

@ -0,0 +1,88 @@
/*
* Minosoft
* Copyright (C) 2020-2022 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.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.world.queue.meshing.tasks
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
import de.bixilon.kutil.concurrent.pool.DefaultThreadPool
import de.bixilon.minosoft.data.world.positions.ChunkPosition
import de.bixilon.minosoft.data.world.positions.SectionHeight
import de.bixilon.minosoft.gui.rendering.world.WorldRenderer
class MeshPrepareTaskManager(
private val renderer: WorldRenderer,
val max: Int = maxOf(DefaultThreadPool.threadCount - 2, 1),
) {
private val tasks: MutableSet<MeshPrepareTask> = mutableSetOf() // current running section preparing tasks
private val lock = SimpleLock()
val size: Int get() = tasks.size
fun add(task: MeshPrepareTask) {
lock.lock()
tasks += task
lock.unlock()
DefaultThreadPool += task.runnable
}
operator fun plusAssign(task: MeshPrepareTask) = add(task)
fun remove(task: MeshPrepareTask) {
lock.lock()
tasks -= task
lock.unlock()
}
operator fun minusAssign(task: MeshPrepareTask) = remove(task)
fun interruptAll() {
lock.acquire()
for (task in tasks) {
task.runnable.interrupt()
}
lock.release()
}
fun interrupt(position: ChunkPosition) {
lock.acquire()
for (task in tasks) {
if (task.chunkPosition == position) {
task.runnable.interrupt()
}
}
lock.release()
}
fun interrupt(position: ChunkPosition, height: SectionHeight) {
lock.acquire()
for (task in tasks) {
if (task.chunkPosition == position && task.sectionHeight == height) {
task.runnable.interrupt()
}
}
lock.release()
}
fun cleanup() {
lock.acquire()
for (task in tasks) {
if (!renderer.visibilityGraph.isChunkVisible(task.chunkPosition)) {
task.runnable.interrupt()
}
}
lock.release()
}
}