mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-17 03:15:35 -04:00
world renderer: loaded meshes
This commit is contained in:
parent
50dc60d3f1
commit
0e45237ce4
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -93,8 +93,7 @@ class WorldRenderer(
|
|||||||
private val solidSectionPreparer: SolidSectionPreparer = SolidCullSectionPreparer(renderWindow)
|
private val solidSectionPreparer: SolidSectionPreparer = SolidCullSectionPreparer(renderWindow)
|
||||||
private val fluidSectionPreparer: FluidSectionPreparer = FluidCullSectionPreparer(renderWindow)
|
private val fluidSectionPreparer: FluidSectionPreparer = FluidCullSectionPreparer(renderWindow)
|
||||||
|
|
||||||
val loadedMeshes: MutableMap<Vec2i, Int2ObjectOpenHashMap<WorldMesh>> = mutableMapOf() // all prepared (and up to date) meshes
|
val loaded = LoadedMeshes(this)
|
||||||
val loadedMeshesLock = SimpleLock()
|
|
||||||
|
|
||||||
val maxPreparingTasks = maxOf(DefaultThreadPool.threadCount - 2, 1)
|
val maxPreparingTasks = maxOf(DefaultThreadPool.threadCount - 2, 1)
|
||||||
private val preparingTasks: MutableSet<SectionPrepareTask> = mutableSetOf() // current running section preparing tasks
|
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
|
val maxMeshesToLoad = if (SystemInformation.RUNTIME.maxMemory() > 1_000_000_000) 150 else 80
|
||||||
private val loadingQueue = MeshLoadingQueue(this)
|
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, ...)
|
// 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
|
private var clearVisibleNextFrame = false
|
||||||
@ -122,13 +121,12 @@ class WorldRenderer(
|
|||||||
var cameraChunkPosition = Vec2i.EMPTY
|
var cameraChunkPosition = Vec2i.EMPTY
|
||||||
private var cameraSectionHeight = 0
|
private var cameraSectionHeight = 0
|
||||||
|
|
||||||
val visibleSize: String
|
val visibleSize: String get() = visible.sizeString
|
||||||
get() = visible.sizeString
|
val loadedMeshesSize: Int get() = loaded.size
|
||||||
val loadedMeshesSize: Int by loadedMeshes::size
|
val culledQueuedSize: Int get() = culledQueue.size
|
||||||
val culledQueuedSize: Int by culledQueue::size
|
val meshesToLoadSize: Int get() = loadingQueue.size
|
||||||
val meshesToLoadSize: Int by loadingQueue::size
|
val queueSize: Int get() = queue.size
|
||||||
val queueSize: Int by queue::size
|
val preparingTasksSize: Int get() = preparingTasks.size
|
||||||
val preparingTasksSize: Int by preparingTasks::size
|
|
||||||
|
|
||||||
override fun init(latch: CountUpAndDownLatch) {
|
override fun init(latch: CountUpAndDownLatch) {
|
||||||
renderWindow.modelLoader.load(latch)
|
renderWindow.modelLoader.load(latch)
|
||||||
@ -262,17 +260,9 @@ class WorldRenderer(
|
|||||||
queueLock.lock()
|
queueLock.lock()
|
||||||
loadingQueue.lock()
|
loadingQueue.lock()
|
||||||
unloadingQueue.lock()
|
unloadingQueue.lock()
|
||||||
loadedMeshesLock.lock()
|
loaded.lock()
|
||||||
|
|
||||||
val loadedMeshesToRemove: MutableSet<Vec2i> = HashSet()
|
loaded.cleanup(false)
|
||||||
for ((chunkPosition, sections) in loadedMeshes) {
|
|
||||||
if (visibilityGraph.isInViewDistance(chunkPosition)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
loadedMeshesToRemove += chunkPosition
|
|
||||||
unloadingQueue.forceQueue(sections.values)
|
|
||||||
}
|
|
||||||
loadedMeshes -= loadedMeshesToRemove
|
|
||||||
|
|
||||||
val toRemove: MutableSet<Vec2i> = HashSet()
|
val toRemove: MutableSet<Vec2i> = HashSet()
|
||||||
for (chunkPosition in culledQueue.keys) {
|
for (chunkPosition in culledQueue.keys) {
|
||||||
@ -296,7 +286,7 @@ class WorldRenderer(
|
|||||||
}
|
}
|
||||||
preparingTasksLock.release()
|
preparingTasksLock.release()
|
||||||
|
|
||||||
loadedMeshesLock.unlock()
|
loaded.unlock()
|
||||||
queueLock.unlock()
|
queueLock.unlock()
|
||||||
culledQueueLock.unlock()
|
culledQueueLock.unlock()
|
||||||
loadingQueue.unlock()
|
loadingQueue.unlock()
|
||||||
@ -331,21 +321,9 @@ class WorldRenderer(
|
|||||||
culledQueueLock.lock()
|
culledQueueLock.lock()
|
||||||
queueLock.lock()
|
queueLock.lock()
|
||||||
loadingQueue.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()
|
preparingTasksLock.acquire()
|
||||||
for (task in preparingTasks) {
|
for (task in preparingTasks) {
|
||||||
@ -353,7 +331,17 @@ class WorldRenderer(
|
|||||||
}
|
}
|
||||||
preparingTasksLock.release()
|
preparingTasksLock.release()
|
||||||
|
|
||||||
loadedMeshesLock.unlock()
|
unloadingQueue.lock()
|
||||||
|
loaded.clear(false)
|
||||||
|
|
||||||
|
culledQueue.clear()
|
||||||
|
queue.clear()
|
||||||
|
queueSet.clear()
|
||||||
|
loadingQueue.clear(false)
|
||||||
|
|
||||||
|
clearVisibleNextFrame = true
|
||||||
|
|
||||||
|
loaded.unlock()
|
||||||
queueLock.unlock()
|
queueLock.unlock()
|
||||||
culledQueueLock.unlock()
|
culledQueueLock.unlock()
|
||||||
loadingQueue.unlock()
|
loadingQueue.unlock()
|
||||||
@ -364,16 +352,8 @@ class WorldRenderer(
|
|||||||
queueLock.lock()
|
queueLock.lock()
|
||||||
loadingQueue.lock()
|
loadingQueue.lock()
|
||||||
unloadingQueue.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()
|
preparingTasksLock.acquire()
|
||||||
for (task in preparingTasks) {
|
for (task in preparingTasks) {
|
||||||
@ -382,9 +362,18 @@ class WorldRenderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
preparingTasksLock.release()
|
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()
|
culledQueueLock.unlock()
|
||||||
loadingQueue.unlock()
|
loadingQueue.unlock()
|
||||||
unloadingQueue.unlock()
|
unloadingQueue.unlock()
|
||||||
@ -481,13 +470,8 @@ class WorldRenderer(
|
|||||||
queueLock.lock()
|
queueLock.lock()
|
||||||
loadingQueue.lock()
|
loadingQueue.lock()
|
||||||
unloadingQueue.lock()
|
unloadingQueue.lock()
|
||||||
loadedMeshesLock.lock()
|
loaded.lock()
|
||||||
loadedMeshes[item.chunkPosition]?.let {
|
loaded.unload(item.chunkPosition, item.sectionHeight, false)
|
||||||
unloadingQueue.forceQueue(it.remove(item.sectionHeight) ?: return@let, false)
|
|
||||||
if (it.isEmpty()) {
|
|
||||||
loadedMeshes -= item.chunkPosition
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
culledQueue[item.chunkPosition]?.let {
|
culledQueue[item.chunkPosition]?.let {
|
||||||
it.remove(item.sectionHeight)
|
it.remove(item.sectionHeight)
|
||||||
@ -510,7 +494,7 @@ class WorldRenderer(
|
|||||||
}
|
}
|
||||||
preparingTasksLock.release()
|
preparingTasksLock.release()
|
||||||
|
|
||||||
loadedMeshesLock.unlock()
|
loaded.unlock()
|
||||||
queueLock.unlock()
|
queueLock.unlock()
|
||||||
culledQueueLock.unlock()
|
culledQueueLock.unlock()
|
||||||
loadingQueue.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()
|
val neighbours = chunk.neighbours.get()
|
||||||
if (neighbours == null || !chunk.isFullyLoaded || renderWindow.state == RenderingStates.PAUSED) {
|
if (neighbours == null || !chunk.isFullyLoaded || renderWindow.state == RenderingStates.PAUSED) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.loadedMeshesLock.acquire()
|
|
||||||
if (this.loadedMeshes.containsKey(chunkPosition)) {
|
// should not queue, it is already loaded
|
||||||
// ToDo: this also ignores light updates
|
if (chunkPosition in loaded) {
|
||||||
this.loadedMeshesLock.release()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.loadedMeshesLock.release()
|
|
||||||
|
|
||||||
|
|
||||||
// ToDo: Check if chunk is visible (not section, chunk)
|
// ToDo: Check if chunk is visible (not section, chunk)
|
||||||
@ -669,20 +651,7 @@ class WorldRenderer(
|
|||||||
|
|
||||||
val visible = VisibleMeshes(cameraPosition, this.visible)
|
val visible = VisibleMeshes(cameraPosition, this.visible)
|
||||||
|
|
||||||
loadedMeshesLock.acquire()
|
loaded.collect(visible)
|
||||||
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()
|
|
||||||
|
|
||||||
culledQueueLock.acquire() // The queue method needs the full lock of the culledQueue
|
culledQueueLock.acquire() // The queue method needs the full lock of the culledQueue
|
||||||
val nextQueue: MutableMap<Vec2i, Pair<Chunk, IntOpenHashSet>> = mutableMapOf()
|
val nextQueue: MutableMap<Vec2i, Pair<Chunk, IntOpenHashSet>> = mutableMapOf()
|
||||||
|
@ -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
|
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
package de.bixilon.minosoft.gui.rendering.world.queue
|
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.concurrent.lock.simple.SimpleLock
|
||||||
import de.bixilon.kutil.time.TimeUtil
|
import de.bixilon.kutil.time.TimeUtil
|
||||||
import de.bixilon.minosoft.data.world.positions.ChunkPosition
|
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)
|
val maxTime = renderer.maxBusyTime // If the player is still, then we can load more chunks (to not cause lags)
|
||||||
|
|
||||||
|
|
||||||
renderer.loadedMeshesLock.lock()
|
renderer.loaded.lock()
|
||||||
while (meshes.isNotEmpty() && (TimeUtil.millis() - start < maxTime)) {
|
var meshes: Int2ObjectOpenHashMap<WorldMesh> = unsafeNull()
|
||||||
val mesh = meshes.removeAt(0)
|
var position: ChunkPosition? = null
|
||||||
|
while (this.meshes.isNotEmpty() && (TimeUtil.millis() - start < maxTime)) {
|
||||||
|
val mesh = this.meshes.removeAt(0)
|
||||||
this.positions -= mesh.chunkPosition
|
this.positions -= mesh.chunkPosition
|
||||||
|
|
||||||
mesh.load()
|
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 {
|
meshes.put(mesh.sectionHeight, mesh)?.let {
|
||||||
renderer.visible.removeMesh(it)
|
renderer.visible.removeMesh(it)
|
||||||
@ -64,7 +71,7 @@ class MeshLoadingQueue(
|
|||||||
renderer.visible.addMesh(mesh)
|
renderer.visible.addMesh(mesh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
renderer.loadedMeshesLock.unlock()
|
renderer.loaded.unlock()
|
||||||
|
|
||||||
lock.unlock()
|
lock.unlock()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user