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 += TextElement(guiRenderer, TextComponent(RunConfiguration.APPLICATION_NAME, ChatColors.RED))
layout += AutoTextElement(guiRenderer, 1) { "FPS §d${renderWindow.renderStats.smoothAvgFPS.rounded10}" } layout += AutoTextElement(guiRenderer, 1) { "FPS §d${renderWindow.renderStats.smoothAvgFPS.rounded10}" }
renderWindow.renderer[WorldRenderer]?.apply { 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 { 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.SolidSectionPreparer
import de.bixilon.minosoft.gui.rendering.world.preparer.cull.FluidCullSectionPreparer 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.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.MeshLoadingQueue
import de.bixilon.minosoft.gui.rendering.world.queue.MeshUnloadingQueue 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.WorldShader
import de.bixilon.minosoft.gui.rendering.world.shader.WorldTextShader import de.bixilon.minosoft.gui.rendering.world.shader.WorldTextShader
import de.bixilon.minosoft.modding.event.events.RespawnEvent import de.bixilon.minosoft.modding.event.events.RespawnEvent
@ -108,12 +109,12 @@ class WorldRenderer(
var cameraChunkPosition = Vec2i.EMPTY var cameraChunkPosition = Vec2i.EMPTY
var cameraSectionHeight = 0 var cameraSectionHeight = 0
val visibleSize: String get() = visible.sizeString @Deprecated("alias?", ReplaceWith("visible.sizeString")) val visibleSize: String get() = visible.sizeString
val loadedMeshesSize: Int get() = loaded.size @Deprecated("alias?", ReplaceWith("loaded.size")) val loadedMeshesSize: Int get() = loaded.size
val culledQueuedSize: Int get() = culledQueue.size @Deprecated("alias?") val culledQueuedSize: Int get() = culledQueue.size
val meshesToLoadSize: Int get() = loadingQueue.size @Deprecated("alias?", ReplaceWith("loadingQueue.size")) val meshesToLoadSize: Int get() = loadingQueue.size
val queueSize: Int get() = queue.size @Deprecated("alias?", ReplaceWith("queue.size")) val queueSize: Int get() = queue.size
val preparingTasksSize: Int get() = queue.preparingTasks.size @Deprecated("alias?", ReplaceWith("queue.tasks.size")) val preparingTasksSize: Int get() = queue.tasks.size
override fun init(latch: CountUpAndDownLatch) { override fun init(latch: CountUpAndDownLatch) {
renderWindow.modelLoader.load(latch) renderWindow.modelLoader.load(latch)
@ -262,8 +263,8 @@ class WorldRenderer(
queue.cleanup() queue.cleanup()
queue.tasks.cleanup()
loadingQueue.cleanup(false) loadingQueue.cleanup(false)
queue.interruptCleanup()
loaded.unlock() loaded.unlock()
@ -305,7 +306,7 @@ class WorldRenderer(
queue.interrupt() queue.tasks.interruptAll()
unloadingQueue.lock() unloadingQueue.lock()
loaded.clear(false) loaded.clear(false)
@ -330,7 +331,7 @@ class WorldRenderer(
loaded.lock() loaded.lock()
queue.interrupt(chunkPosition) queue.tasks.interrupt(chunkPosition)
culledQueue.remove(chunkPosition) culledQueue.remove(chunkPosition)
@ -348,7 +349,7 @@ class WorldRenderer(
queue.unlock() queue.unlock()
} }
fun prepareItem(item: WorldQueueItem, task: SectionPrepareTask, runnable: ThreadPoolRunnable) { fun prepareItem(item: WorldQueueItem, task: MeshPrepareTask, runnable: ThreadPoolRunnable) {
try { try {
val chunk = item.chunk ?: world[item.chunkPosition] ?: return val chunk = item.chunk ?: world[item.chunkPosition] ?: return
val section = chunk[item.sectionHeight] ?: return val section = chunk[item.sectionHeight] ?: return
@ -378,7 +379,7 @@ class WorldRenderer(
} finally { } finally {
task.runnable.interruptable = false task.runnable.interruptable = false
if (Thread.interrupted()) throw InterruptedException() if (Thread.interrupted()) throw InterruptedException()
queue -= task queue.tasks -= task
queue.work() queue.work()
} }
} }
@ -398,11 +399,11 @@ class WorldRenderer(
} }
} }
queue.remove(item.chunkPosition, item.sectionHeight) queue.remove(item)
loadingQueue.abort(item.chunkPosition, false) loadingQueue.abort(item.chunkPosition, false)
queue.interrupt(item.chunkPosition, item.sectionHeight) queue.tasks.interrupt(item.chunkPosition, item.sectionHeight)
loaded.unlock() loaded.unlock()
queue.unlock() queue.unlock()

View File

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

View File

@ -11,25 +11,24 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft. * 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.kotlinglm.vec3.Vec3i
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock 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.ThreadPool
import de.bixilon.kutil.concurrent.pool.ThreadPoolRunnable import de.bixilon.kutil.concurrent.pool.ThreadPoolRunnable
import de.bixilon.minosoft.data.world.positions.ChunkPosition 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.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.WorldQueueItem
import de.bixilon.minosoft.gui.rendering.world.WorldRenderer 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 import de.bixilon.minosoft.util.SystemInformation
class ChunkMeshingQueue( class ChunkMeshingQueue(
private val renderer: WorldRenderer, 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 val maxMeshesToLoad = if (SystemInformation.RUNTIME.maxMemory() > 1_000_000_000) 150 else 80
@Volatile @Volatile
@ -39,9 +38,6 @@ class ChunkMeshingQueue(
val lock = SimpleLock() val lock = SimpleLock()
val preparingTasks: MutableSet<SectionPrepareTask> = mutableSetOf() // current running section preparing tasks
val preparingTasksLock = SimpleLock()
val size: Int get() = queue.size val size: Int get() = queue.size
@ -64,8 +60,8 @@ class ChunkMeshingQueue(
fun work() { fun work() {
if (working) return // do not work twice if (working) return // do not work twice
val size = preparingTasks.size val size = tasks.size
if (queue.isEmpty() || size >= maxPreparingTasks || renderer.loadingQueue.size >= maxMeshesToLoad) { if (queue.isEmpty() || size >= tasks.max || renderer.loadingQueue.size >= maxMeshesToLoad) {
return return
} }
working = true working = true
@ -73,24 +69,20 @@ class ChunkMeshingQueue(
val items: MutableList<WorldQueueItem> = mutableListOf() val items: MutableList<WorldQueueItem> = mutableListOf()
lock.lock() lock.lock()
for (i in 0 until maxPreparingTasks - size) { for (i in 0 until tasks.max - size) {
if (queue.isEmpty()) { if (queue.isEmpty()) {
break break
} }
val item = queue.removeFirst() val item = queue.removeFirst()
set.remove(item) set -= item
items += item items += item
} }
lock.unlock() lock.unlock()
for (item in items) { 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 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
task.runnable.runnable = Runnable { val task = MeshPrepareTask(item.chunkPosition, item.sectionHeight, runnable)
renderer.prepareItem(item, task, task.runnable) task.runnable.runnable = Runnable { renderer.prepareItem(item, task, task.runnable) }
} tasks += task
preparingTasksLock.lock()
preparingTasks += task
preparingTasksLock.unlock()
DefaultThreadPool += task.runnable
} }
working = false working = false
} }
@ -128,66 +120,28 @@ class ChunkMeshingQueue(
this.lock.unlock() 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() { fun clear() {
this.lock.lock()
this.queue.clear()
this.set.clear()
this.lock.unlock()
} }
fun remove(task: SectionPrepareTask) {
preparingTasksLock.lock() fun remove(item: WorldQueueItem) {
preparingTasks -= task this.lock.lock()
preparingTasksLock.unlock() if (this.set.remove(item)) {
this.queue -= item
} }
this.lock.unlock()
operator fun minusAssign(task: SectionPrepareTask) = remove(task)
fun remove(position: ChunkPosition, height: SectionHeight) {
} }
fun queue(item: WorldQueueItem) { fun queue(item: WorldQueueItem) {
lock.lock() lock.lock()
if (set.remove(item)) { 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) { if (item.chunkPosition == renderer.cameraChunkPosition) {
queue.add(0, item) queue.add(0, item)

View File

@ -11,12 +11,12 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft. * 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.kotlinglm.vec2.Vec2i
import de.bixilon.kutil.concurrent.pool.ThreadPoolRunnable import de.bixilon.kutil.concurrent.pool.ThreadPoolRunnable
class SectionPrepareTask( class MeshPrepareTask(
val chunkPosition: Vec2i, val chunkPosition: Vec2i,
val sectionHeight: Int, val sectionHeight: Int,
val runnable: ThreadPoolRunnable, 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()
}
}