From 592aef6bdad50ff400fbf4cb1cc87064101b1e16 Mon Sep 17 00:00:00 2001 From: Bixilon Date: Thu, 22 Dec 2022 22:11:15 +0100 Subject: [PATCH] world renderer: rework preparing tasks --- .../gui/hud/elements/other/DebugHUDElement.kt | 2 +- .../gui/rendering/world/WorldRenderer.kt | 29 +++--- .../rendering/world/queue/MeshLoadingQueue.kt | 1 - .../queue/{ => meshing}/ChunkMeshingQueue.kt | 92 +++++-------------- .../meshing/tasks/MeshPrepareTask.kt} | 4 +- .../meshing/tasks/MeshPrepareTaskManager.kt | 88 ++++++++++++++++++ 6 files changed, 129 insertions(+), 87 deletions(-) rename src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/{ => meshing}/ChunkMeshingQueue.kt (58%) rename src/main/java/de/bixilon/minosoft/gui/rendering/world/{SectionPrepareTask.kt => queue/meshing/tasks/MeshPrepareTask.kt} (91%) create mode 100644 src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/tasks/MeshPrepareTaskManager.kt diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/other/DebugHUDElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/other/DebugHUDElement.kt index 64d79ec02..0b0058eff 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/other/DebugHUDElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/other/DebugHUDElement.kt @@ -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 { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRenderer.kt index 5f8ae7d28..f63844977 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRenderer.kt @@ -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() diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshLoadingQueue.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshLoadingQueue.kt index 5189171a0..450a25961 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshLoadingQueue.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshLoadingQueue.kt @@ -25,7 +25,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap class MeshLoadingQueue( private val renderer: WorldRenderer, ) { - private val meshes: MutableList = mutableListOf() // prepared meshes, that can be loaded in the (next) frame private val positions: MutableSet = HashSet() private val lock = SimpleLock() diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/ChunkMeshingQueue.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/ChunkMeshingQueue.kt similarity index 58% rename from src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/ChunkMeshingQueue.kt rename to src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/ChunkMeshingQueue.kt index a7d0213a0..ed3de48d2 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/ChunkMeshingQueue.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/ChunkMeshingQueue.kt @@ -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 = 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 = 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() - } - - operator fun minusAssign(task: SectionPrepareTask) = remove(task) - - - fun remove(position: ChunkPosition, height: SectionHeight) { + fun remove(item: WorldQueueItem) { + this.lock.lock() + if (this.set.remove(item)) { + this.queue -= item + } + 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) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/SectionPrepareTask.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/tasks/MeshPrepareTask.kt similarity index 91% rename from src/main/java/de/bixilon/minosoft/gui/rendering/world/SectionPrepareTask.kt rename to src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/tasks/MeshPrepareTask.kt index 40230cbd9..dbb811bb8 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/world/SectionPrepareTask.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/tasks/MeshPrepareTask.kt @@ -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, diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/tasks/MeshPrepareTaskManager.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/tasks/MeshPrepareTaskManager.kt new file mode 100644 index 000000000..1a8b3aa08 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/tasks/MeshPrepareTaskManager.kt @@ -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 . + * + * 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 = 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() + } +}