world renderer: loaded meshes

This commit is contained in:
Bixilon 2022-12-22 18:14:41 +01:00
parent 50dc60d3f1
commit 0e45237ce4
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
4 changed files with 194 additions and 79 deletions

View File

@ -0,0 +1,123 @@
/*
* 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
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
import de.bixilon.minosoft.data.world.positions.ChunkPosition
import de.bixilon.minosoft.gui.rendering.world.mesh.VisibleMeshes
import de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
class LoadedMeshes(
private val renderer: WorldRenderer,
) {
val meshes: MutableMap<Vec2i, Int2ObjectOpenHashMap<WorldMesh>> = mutableMapOf() // all prepared (and up to date) meshes
private val lock = SimpleLock()
val size: Int get() = meshes.size
fun cleanup(lock: Boolean) {
val remove: MutableSet<Vec2i> = HashSet()
if (lock) this.lock.lock()
for ((chunkPosition, sections) in meshes) {
if (renderer.visibilityGraph.isInViewDistance(chunkPosition)) {
continue
}
remove += chunkPosition
renderer.unloadingQueue.forceQueue(sections.values)
}
meshes -= remove
if (lock) this.lock.unlock()
}
fun clear(lock: Boolean) {
if (lock) this.lock.lock()
for (sections in meshes.values) {
renderer.unloadingQueue.forceQueue(sections.values, lock)
}
if (lock) this.lock.unlock()
}
fun unload(position: ChunkPosition, lock: Boolean) {
if (lock) this.lock.lock()
val meshes = this.meshes.remove(position)
if (meshes != null) {
renderer.unloadingQueue.forceQueue(meshes.values, lock)
}
if (lock) this.lock.unlock()
}
fun unload(position: ChunkPosition, sectionHeight: Int, lock: Boolean) {
if (lock) this.lock.lock()
val meshes = this.meshes[position]
if (meshes != null) {
meshes.remove(sectionHeight)?.let {
renderer.unloadingQueue.forceQueue(it, lock)
if (meshes.isEmpty()) {
this.meshes.remove(position)
}
}
}
if (lock) this.lock.unlock()
}
operator fun contains(position: ChunkPosition): Boolean {
lock.acquire()
val contains = position in this.meshes
lock.release()
return contains
}
fun collect(visible: VisibleMeshes) {
lock.acquire()
for ((chunkPosition, meshes) in this.meshes) {
if (!renderer.visibilityGraph.isChunkVisible(chunkPosition)) {
continue
}
for (entry in meshes.int2ObjectEntrySet()) {
val mesh = entry.value
if (!renderer.visibilityGraph.isSectionVisible(chunkPosition, entry.intKey, mesh.minPosition, mesh.maxPosition, false)) {
continue
}
visible.addMesh(mesh)
}
}
lock.release()
}
fun lock() {
this.lock.lock()
}
fun unlock() {
this.lock.unlock()
}
}

View File

@ -93,8 +93,7 @@ class WorldRenderer(
private val solidSectionPreparer: SolidSectionPreparer = SolidCullSectionPreparer(renderWindow)
private val fluidSectionPreparer: FluidSectionPreparer = FluidCullSectionPreparer(renderWindow)
val loadedMeshes: MutableMap<Vec2i, Int2ObjectOpenHashMap<WorldMesh>> = mutableMapOf() // all prepared (and up to date) meshes
val loadedMeshesLock = SimpleLock()
val loaded = LoadedMeshes(this)
val maxPreparingTasks = maxOf(DefaultThreadPool.threadCount - 2, 1)
private val preparingTasks: MutableSet<SectionPrepareTask> = mutableSetOf() // current running section preparing tasks
@ -110,7 +109,7 @@ class WorldRenderer(
val maxMeshesToLoad = if (SystemInformation.RUNTIME.maxMemory() > 1_000_000_000) 150 else 80
private val loadingQueue = MeshLoadingQueue(this)
private val unloadingQueue = MeshUnloadingQueue(this)
val unloadingQueue = MeshUnloadingQueue(this)
// all meshes that will be rendered in the next frame (might be changed, when the frustum changes or a chunk gets loaded, ...)
private var clearVisibleNextFrame = false
@ -122,13 +121,12 @@ class WorldRenderer(
var cameraChunkPosition = Vec2i.EMPTY
private var cameraSectionHeight = 0
val visibleSize: String
get() = visible.sizeString
val loadedMeshesSize: Int by loadedMeshes::size
val culledQueuedSize: Int by culledQueue::size
val meshesToLoadSize: Int by loadingQueue::size
val queueSize: Int by queue::size
val preparingTasksSize: Int by preparingTasks::size
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() = preparingTasks.size
override fun init(latch: CountUpAndDownLatch) {
renderWindow.modelLoader.load(latch)
@ -262,17 +260,9 @@ class WorldRenderer(
queueLock.lock()
loadingQueue.lock()
unloadingQueue.lock()
loadedMeshesLock.lock()
loaded.lock()
val loadedMeshesToRemove: MutableSet<Vec2i> = HashSet()
for ((chunkPosition, sections) in loadedMeshes) {
if (visibilityGraph.isInViewDistance(chunkPosition)) {
continue
}
loadedMeshesToRemove += chunkPosition
unloadingQueue.forceQueue(sections.values)
}
loadedMeshes -= loadedMeshesToRemove
loaded.cleanup(false)
val toRemove: MutableSet<Vec2i> = HashSet()
for (chunkPosition in culledQueue.keys) {
@ -296,7 +286,7 @@ class WorldRenderer(
}
preparingTasksLock.release()
loadedMeshesLock.unlock()
loaded.unlock()
queueLock.unlock()
culledQueueLock.unlock()
loadingQueue.unlock()
@ -331,21 +321,9 @@ class WorldRenderer(
culledQueueLock.lock()
queueLock.lock()
loadingQueue.lock()
loadedMeshesLock.lock()
loaded.lock()
unloadingQueue.lock()
for (sections in loadedMeshes.values) {
unloadingQueue.forceQueue(sections.values, false)
}
unloadingQueue.unlock()
culledQueue.clear()
loadedMeshes.clear()
queue.clear()
queueSet.clear()
loadingQueue.clear()
clearVisibleNextFrame = true
preparingTasksLock.acquire()
for (task in preparingTasks) {
@ -353,7 +331,17 @@ class WorldRenderer(
}
preparingTasksLock.release()
loadedMeshesLock.unlock()
unloadingQueue.lock()
loaded.clear(false)
culledQueue.clear()
queue.clear()
queueSet.clear()
loadingQueue.clear(false)
clearVisibleNextFrame = true
loaded.unlock()
queueLock.unlock()
culledQueueLock.unlock()
loadingQueue.unlock()
@ -364,16 +352,8 @@ class WorldRenderer(
queueLock.lock()
loadingQueue.lock()
unloadingQueue.lock()
loadedMeshesLock.lock()
loaded.lock()
val meshes = loadedMeshes.remove(chunkPosition)
culledQueue.remove(chunkPosition)
queue.removeAll { it.chunkPosition == chunkPosition }
queueSet.removeAll { it.chunkPosition == chunkPosition }
loadingQueue.abort(chunkPosition, false)
preparingTasksLock.acquire()
for (task in preparingTasks) {
@ -382,9 +362,18 @@ class WorldRenderer(
}
}
preparingTasksLock.release()
meshes?.let { unloadingQueue.forceQueue(meshes.values, false) }
loadedMeshesLock.unlock()
culledQueue.remove(chunkPosition)
queue.removeAll { it.chunkPosition == chunkPosition }
queueSet.removeAll { it.chunkPosition == chunkPosition }
loadingQueue.abort(chunkPosition, false)
val meshes = loaded.unload(chunkPosition, false)
loaded.unlock()
culledQueueLock.unlock()
loadingQueue.unlock()
unloadingQueue.unlock()
@ -481,13 +470,8 @@ class WorldRenderer(
queueLock.lock()
loadingQueue.lock()
unloadingQueue.lock()
loadedMeshesLock.lock()
loadedMeshes[item.chunkPosition]?.let {
unloadingQueue.forceQueue(it.remove(item.sectionHeight) ?: return@let, false)
if (it.isEmpty()) {
loadedMeshes -= item.chunkPosition
}
}
loaded.lock()
loaded.unload(item.chunkPosition, item.sectionHeight, false)
culledQueue[item.chunkPosition]?.let {
it.remove(item.sectionHeight)
@ -510,7 +494,7 @@ class WorldRenderer(
}
preparingTasksLock.release()
loadedMeshesLock.unlock()
loaded.unlock()
queueLock.unlock()
culledQueueLock.unlock()
loadingQueue.unlock()
@ -565,18 +549,16 @@ class WorldRenderer(
}
}
private fun queueChunk(chunkPosition: Vec2i, chunk: Chunk = world.chunks[chunkPosition]!!) {
private fun queueChunk(chunkPosition: Vec2i, chunk: Chunk) {
val neighbours = chunk.neighbours.get()
if (neighbours == null || !chunk.isFullyLoaded || renderWindow.state == RenderingStates.PAUSED) {
return
}
this.loadedMeshesLock.acquire()
if (this.loadedMeshes.containsKey(chunkPosition)) {
// ToDo: this also ignores light updates
this.loadedMeshesLock.release()
// should not queue, it is already loaded
if (chunkPosition in loaded) {
return
}
this.loadedMeshesLock.release()
// ToDo: Check if chunk is visible (not section, chunk)
@ -669,20 +651,7 @@ class WorldRenderer(
val visible = VisibleMeshes(cameraPosition, this.visible)
loadedMeshesLock.acquire()
for ((chunkPosition, meshes) in this.loadedMeshes) {
if (!visibilityGraph.isChunkVisible(chunkPosition)) {
continue
}
for (entry in meshes.int2ObjectEntrySet()) {
val mesh = entry.value
if (visibilityGraph.isSectionVisible(chunkPosition, entry.intKey, mesh.minPosition, mesh.maxPosition, false)) {
visible.addMesh(mesh)
}
}
}
loadedMeshesLock.release()
loaded.collect(visible)
culledQueueLock.acquire() // The queue method needs the full lock of the culledQueue
val nextQueue: MutableMap<Vec2i, Pair<Chunk, IntOpenHashSet>> = mutableMapOf()

View File

@ -0,0 +1,16 @@
/*
* 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
class ChunkMeshingQueue

View File

@ -13,6 +13,7 @@
package de.bixilon.minosoft.gui.rendering.world.queue
import de.bixilon.kutil.cast.CastUtil.unsafeNull
import de.bixilon.kutil.concurrent.lock.simple.SimpleLock
import de.bixilon.kutil.time.TimeUtil
import de.bixilon.minosoft.data.world.positions.ChunkPosition
@ -44,14 +45,20 @@ class MeshLoadingQueue(
val maxTime = renderer.maxBusyTime // If the player is still, then we can load more chunks (to not cause lags)
renderer.loadedMeshesLock.lock()
while (meshes.isNotEmpty() && (TimeUtil.millis() - start < maxTime)) {
val mesh = meshes.removeAt(0)
renderer.loaded.lock()
var meshes: Int2ObjectOpenHashMap<WorldMesh> = unsafeNull()
var position: ChunkPosition? = null
while (this.meshes.isNotEmpty() && (TimeUtil.millis() - start < maxTime)) {
val mesh = this.meshes.removeAt(0)
this.positions -= mesh.chunkPosition
mesh.load()
val meshes = renderer.loadedMeshes.getOrPut(mesh.chunkPosition) { Int2ObjectOpenHashMap() }
if (position != mesh.chunkPosition) {
meshes = renderer.loaded.meshes.getOrPut(mesh.chunkPosition) { Int2ObjectOpenHashMap() }
position = mesh.chunkPosition
}
meshes.put(mesh.sectionHeight, mesh)?.let {
renderer.visible.removeMesh(it)
@ -64,7 +71,7 @@ class MeshLoadingQueue(
renderer.visible.addMesh(mesh)
}
}
renderer.loadedMeshesLock.unlock()
renderer.loaded.unlock()
lock.unlock()