world-renderer: split visible meshes, sort them

This commit is contained in:
Bixilon 2021-11-13 17:58:53 +01:00
parent dcb058c960
commit 611f55fd82
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
8 changed files with 67 additions and 35 deletions

View File

@ -48,7 +48,7 @@ object RenderConstants {
const val FRUSTUM_CULLING_ENABLED = true const val FRUSTUM_CULLING_ENABLED = true
const val SHOW_FPS_IN_WINDOW_TITLE = true const val SHOW_FPS_IN_WINDOW_TITLE = true
const val MAXIMUM_QUEUE_TIME_PER_FRAME = 100L const val MAXIMUM_QUEUE_TIME_PER_FRAME = 20L
const val DISABLE_LIGHTING = false const val DISABLE_LIGHTING = false

View File

@ -22,6 +22,7 @@ import de.bixilon.minosoft.data.world.World
import de.bixilon.minosoft.gui.rendering.RenderWindow import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.Renderer import de.bixilon.minosoft.gui.rendering.Renderer
import de.bixilon.minosoft.gui.rendering.RendererBuilder import de.bixilon.minosoft.gui.rendering.RendererBuilder
import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMesh
import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMeshes import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMeshes
import de.bixilon.minosoft.gui.rendering.block.preparer.AbstractSectionPreparer import de.bixilon.minosoft.gui.rendering.block.preparer.AbstractSectionPreparer
import de.bixilon.minosoft.gui.rendering.block.preparer.GenericSectionPreparer import de.bixilon.minosoft.gui.rendering.block.preparer.GenericSectionPreparer
@ -67,19 +68,20 @@ class WorldRenderer(
private val sectionPreparer: AbstractSectionPreparer = GenericSectionPreparer(renderWindow) private val sectionPreparer: AbstractSectionPreparer = GenericSectionPreparer(renderWindow)
private val lightMap = LightMap(connection) private val lightMap = LightMap(connection)
private val meshes: SynchronizedMap<Vec2i, SynchronizedMap<Int, ChunkSectionMeshes>> = synchronizedMapOf() // all prepared (and up to date) meshes private val meshes: SynchronizedMap<Vec2i, SynchronizedMap<Int, ChunkSectionMeshes>> = synchronizedMapOf() // all prepared (and up to date) meshes
private var visibleMeshes: MutableSet<ChunkSectionMeshes> = mutableSetOf() // ToDo: Split in opaque, transparent, translucent meshes and sort (opaque and transparent front to back, translucent back to front)
private var incomplete: MutableSet<Vec2i> = synchronizedSetOf() // Queue of chunk positions that can not be rendered yet (data not complete or neighbours not completed yet) private var incomplete: MutableSet<Vec2i> = synchronizedSetOf() // Queue of chunk positions that can not be rendered yet (data not complete or neighbours not completed yet)
private var queue: MutableMap<Vec2i, MutableSet<Int>> = synchronizedMapOf() // Chunk sections that can be prepared or have changed, but are not required to get rendered yet (i.e. culled chunks) private var queue: MutableMap<Vec2i, MutableSet<Int>> = synchronizedMapOf() // Chunk sections that can be prepared or have changed, but are not required to get rendered yet (i.e. culled chunks)
private var visibleOpaque: MutableList<ChunkSectionMesh> = mutableListOf()
private var visibleTranslucent: MutableList<ChunkSectionMesh> = mutableListOf()
private var visibleTransparent: MutableList<ChunkSectionMesh> = mutableListOf()
val visibleSize: Int
get() = visibleMeshes.size val visibleOpaqueSize: Int by visibleOpaque::size
val preparedSize: Int val visibleTranslucentSize: Int by visibleTranslucent::size
get() = meshes.size val visibleTransparentSize: Int by visibleTransparent::size
val queuedSize: Int val preparedSize: Int by meshes::size
get() = queue.size val queuedSize: Int by queue::size
val incompleteSize: Int val incompleteSize: Int by incomplete::size
get() = incomplete.size
override fun init() { override fun init() {
val asset = Resources.getAssetVersionByVersion(connection.version) val asset = Resources.getAssetVersionByVersion(connection.version)
@ -136,17 +138,29 @@ class WorldRenderer(
incomplete += neighbourPosition incomplete += neighbourPosition
} }
val meshes = this.meshes.remove(chunkPosition) ?: return val meshes = this.meshes.remove(chunkPosition) ?: return
visibleMeshes -= meshes.values
if (meshes.isEmpty()) { if (meshes.isEmpty()) {
return return
} }
renderWindow.queue += { renderWindow.queue += {
for (mesh in meshes.values) { for (mesh in meshes.values) {
removeMesh(mesh)
mesh.unload() mesh.unload()
} }
} }
} }
private fun removeMesh(mesh: ChunkSectionMeshes) {
mesh.opaqueMesh?.let { visibleOpaque -= it }
mesh.translucentMesh?.let { visibleTranslucent -= it }
mesh.transparentMesh?.let { visibleTransparent -= it }
}
private fun addMesh(mesh: ChunkSectionMeshes) {
mesh.opaqueMesh?.let { visibleOpaque += it }
mesh.translucentMesh?.let { visibleTranslucent += it }
mesh.transparentMesh?.let { visibleTransparent += it }
}
/** /**
* @return All 8 fully loaded neighbour chunks or null * @return All 8 fully loaded neighbour chunks or null
*/ */
@ -186,7 +200,7 @@ class WorldRenderer(
neighbourChunks[3].sections!![sectionHeight], neighbourChunks[3].sections!![sectionHeight],
neighbourChunks[4].sections!![sectionHeight], neighbourChunks[4].sections!![sectionHeight],
neighbourChunks[1].sections!![sectionHeight], neighbourChunks[1].sections!![sectionHeight],
neighbourChunks[7].sections!![sectionHeight], neighbourChunks[6].sections!![sectionHeight],
) )
} }
@ -253,7 +267,7 @@ class WorldRenderer(
if (previousMesh != null && !visible) { if (previousMesh != null && !visible) {
meshes.remove(sectionHeight) meshes.remove(sectionHeight)
renderWindow.queue += { renderWindow.queue += {
visibleMeshes -= previousMesh removeMesh(previousMesh)
previousMesh.unload() previousMesh.unload()
} }
} }
@ -281,18 +295,17 @@ class WorldRenderer(
private fun prepareSection(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array<ChunkSection?>, meshes: SynchronizedMap<Int, ChunkSectionMeshes> = this.meshes.getOrPut(chunkPosition) { synchronizedMapOf() }) { private fun prepareSection(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array<ChunkSection?>, meshes: SynchronizedMap<Int, ChunkSectionMeshes> = this.meshes.getOrPut(chunkPosition) { synchronizedMapOf() }) {
val mesh = sectionPreparer.prepare(chunkPosition, sectionHeight, section, neighbours) val mesh = sectionPreparer.prepare(chunkPosition, sectionHeight, section, neighbours)
val currentMesh = meshes.remove(sectionHeight) val previousMesh = meshes.remove(sectionHeight)
renderWindow.queue += { renderWindow.queue += {
if (currentMesh != null) { if (previousMesh != null) {
currentMesh.unload() removeMesh(previousMesh)
this.visibleMeshes -= currentMesh
} }
mesh.load() mesh.load()
meshes[sectionHeight] = mesh meshes[sectionHeight] = mesh
if (isChunkVisible(chunkPosition, sectionHeight, mesh.minPosition, mesh.maxPosition)) { if (isChunkVisible(chunkPosition, sectionHeight, mesh.minPosition, mesh.maxPosition)) {
this.visibleMeshes += mesh addMesh(mesh)
} }
} }
} }
@ -303,8 +316,8 @@ class WorldRenderer(
} }
override fun drawOpaque() { override fun drawOpaque() {
for (mesh in visibleMeshes) { for (mesh in visibleOpaque) {
mesh.opaqueMesh?.draw() mesh.draw()
} }
} }
@ -314,8 +327,8 @@ class WorldRenderer(
} }
override fun drawTranslucent() { override fun drawTranslucent() {
for (mesh in visibleMeshes) { for (mesh in visibleTranslucent) {
mesh.translucentMesh?.draw() mesh.draw()
} }
} }
@ -325,8 +338,8 @@ class WorldRenderer(
} }
override fun drawTransparent() { override fun drawTransparent() {
for (mesh in visibleMeshes) { for (mesh in visibleTransparent) {
mesh.transparentMesh?.draw() mesh.draw()
} }
} }
@ -336,14 +349,18 @@ class WorldRenderer(
} }
private fun onFrustumChange() { private fun onFrustumChange() {
val visible: MutableSet<ChunkSectionMeshes> = mutableSetOf() val visibleOpaque: MutableList<ChunkSectionMesh> = mutableListOf()
val visibleTranslucent: MutableList<ChunkSectionMesh> = mutableListOf()
val visibleTransparent: MutableList<ChunkSectionMesh> = mutableListOf()
for ((chunkPosition, meshes) in this.meshes.toSynchronizedMap()) { for ((chunkPosition, meshes) in this.meshes.toSynchronizedMap()) {
for ((sectionHeight, mesh) in meshes) { for ((sectionHeight, mesh) in meshes) {
if (!isChunkVisible(chunkPosition, sectionHeight, mesh.minPosition, mesh.maxPosition)) { if (!isChunkVisible(chunkPosition, sectionHeight, mesh.minPosition, mesh.maxPosition)) {
continue continue
} }
visible += mesh mesh.opaqueMesh?.let { visibleOpaque += it }
mesh.translucentMesh?.let { visibleTranslucent += it }
mesh.transparentMesh?.let { visibleTransparent += it }
} }
} }
@ -359,8 +376,16 @@ class WorldRenderer(
updateSection(chunkPosition, sectionHeight, chunk, neighbours.unsafeCast(), meshes) updateSection(chunkPosition, sectionHeight, chunk, neighbours.unsafeCast(), meshes)
} }
} }
val cameraPositionLength = connection.player.cameraPosition.length2()
this.visibleMeshes = visible visibleOpaque.sortBy { it.centerLength - cameraPositionLength }
this.visibleOpaque = visibleOpaque
visibleTranslucent.sortBy { cameraPositionLength - it.centerLength }
this.visibleTranslucent = visibleTranslucent
visibleTransparent.sortBy { it.centerLength - cameraPositionLength }
this.visibleTransparent = visibleTransparent
} }

View File

@ -22,7 +22,7 @@ import de.bixilon.minosoft.gui.rendering.util.mesh.MeshStruct
import glm_.vec2.Vec2 import glm_.vec2.Vec2
import glm_.vec3.Vec3 import glm_.vec3.Vec3
class ChunkSectionMesh(renderWindow: RenderWindow) : Mesh(renderWindow, SectionArrayMeshStruct, PrimitiveTypes.QUAD, initialCacheSize = 200000) { class ChunkSectionMesh(renderWindow: RenderWindow, initialCacheSize: Int, val centerLength: Double) : Mesh(renderWindow, SectionArrayMeshStruct, PrimitiveTypes.QUAD, initialCacheSize = initialCacheSize) {
fun addVertex(position: FloatArray, uv: Vec2, texture: AbstractTexture, tintColor: Int, light: Int) { fun addVertex(position: FloatArray, uv: Vec2, texture: AbstractTexture, tintColor: Int, light: Int) {
val transformedUV = texture.renderData?.transformUV(uv) ?: uv val transformedUV = texture.renderData?.transformUV(uv) ?: uv

View File

@ -14,16 +14,22 @@
package de.bixilon.minosoft.gui.rendering.block.mesh package de.bixilon.minosoft.gui.rendering.block.mesh
import de.bixilon.minosoft.gui.rendering.RenderWindow import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.util.VecUtil.of
import glm_.vec2.Vec2i
import glm_.vec3.Vec3d
import glm_.vec3.Vec3i import glm_.vec3.Vec3i
class ChunkSectionMeshes( class ChunkSectionMeshes(
renderWindow: RenderWindow, renderWindow: RenderWindow,
chunkPosition: Vec2i,
sectionHeight: Int,
) { ) {
var opaqueMesh: ChunkSectionMesh? = ChunkSectionMesh(renderWindow) private val centerLength = Vec3d(Vec3i.of(chunkPosition, sectionHeight, Vec3i(8, 8, 8))).length2()
var opaqueMesh: ChunkSectionMesh? = ChunkSectionMesh(renderWindow, 200000, centerLength)
private set private set
var translucentMesh: ChunkSectionMesh? = ChunkSectionMesh(renderWindow) var translucentMesh: ChunkSectionMesh? = ChunkSectionMesh(renderWindow, 100000, centerLength)
private set private set
var transparentMesh: ChunkSectionMesh? = ChunkSectionMesh(renderWindow) var transparentMesh: ChunkSectionMesh? = ChunkSectionMesh(renderWindow, 100000, centerLength)
private set private set
// used for frustum culling // used for frustum culling

View File

@ -16,7 +16,7 @@ class CullSectionPreparer(
) : AbstractSectionPreparer { ) : AbstractSectionPreparer {
override fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array<ChunkSection?>): ChunkSectionMeshes { override fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array<ChunkSection?>): ChunkSectionMeshes {
val mesh = ChunkSectionMeshes(renderWindow) val mesh = ChunkSectionMeshes(renderWindow, chunkPosition, sectionHeight)
val random = Random(0L) val random = Random(0L)
val blocks = section.blocks val blocks = section.blocks

View File

@ -37,8 +37,9 @@ class GreedySectionPreparer(
// base taken from https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/ // base taken from https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/
@Deprecated("TODO")
override fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array<ChunkSection?>): ChunkSectionMeshes { override fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array<ChunkSection?>): ChunkSectionMeshes {
val mesh = ChunkSectionMesh(renderWindow) val mesh = ChunkSectionMesh(renderWindow, 20000, 0.0)
val random = Random(0L) val random = Random(0L)

View File

@ -85,7 +85,7 @@ class DebugHUDElement(hudRenderer: HUDRenderer) : LayoutedHUDElement<GridLayout>
layout += TextElement(hudRenderer, TextComponent(RunConfiguration.VERSION_STRING, ChatColors.RED)) layout += TextElement(hudRenderer, TextComponent(RunConfiguration.VERSION_STRING, ChatColors.RED))
layout += AutoTextElement(hudRenderer, 1) { "FPS ${renderWindow.renderStats.smoothAvgFPS.round10}" } layout += AutoTextElement(hudRenderer, 1) { "FPS ${renderWindow.renderStats.smoothAvgFPS.round10}" }
renderWindow[WorldRenderer]?.apply { renderWindow[WorldRenderer]?.apply {
layout += AutoTextElement(hudRenderer, 1) { "C v=$visibleSize, p=$preparedSize, q=$queuedSize, i=$incompleteSize, t=${connection.world.chunks.size}" } layout += AutoTextElement(hudRenderer, 1) { "C vO=$visibleOpaqueSize, vP=$visibleTransparentSize, vL=$visibleTranslucentSize, p=$preparedSize, q=$queuedSize, i=$incompleteSize, t=${connection.world.chunks.size}" }
} }
layout += AutoTextElement(hudRenderer, 1) { "E t=${connection.world.entities.size}" } layout += AutoTextElement(hudRenderer, 1) { "E t=${connection.world.entities.size}" }

View File

@ -59,7 +59,7 @@ class Camera(
val renderWindow: RenderWindow, val renderWindow: RenderWindow,
) { ) {
var fogColor = Previous(ChatColors.GREEN) var fogColor = Previous(ChatColors.GREEN)
var fogStart = 1000.0f var fogStart = 100.0f
private var mouseSensitivity = Minosoft.config.config.game.controls.moseSensitivity private var mouseSensitivity = Minosoft.config.config.game.controls.moseSensitivity
@Deprecated("", ReplaceWith("connection.player")) @Deprecated("", ReplaceWith("connection.player"))