From 20c5901feace83ba19a2ab9e4e803c63ff0bb4d9 Mon Sep 17 00:00:00 2001 From: Bixilon Date: Wed, 10 Nov 2021 23:52:59 +0100 Subject: [PATCH] wip: world rendering --- .../gui/rendering/block/WorldRenderer.kt | 105 ++++++++++++------ .../block/mesh/ChunkSectionMeshes.kt | 7 ++ .../block/preparer/AbstractSectionPreparer.kt | 3 +- .../block/preparer/CullSectionPreparer.kt | 71 ++++++------ .../block/preparer/GenericSectionPreparer.kt | 5 +- .../block/preparer/GreedySectionPreparer.kt | 3 +- .../gui/hud/elements/other/DebugHUDElement.kt | 2 +- .../gui/rendering/input/camera/Frustum.kt | 11 +- 8 files changed, 133 insertions(+), 74 deletions(-) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/block/WorldRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/block/WorldRenderer.kt index 0cbbb73c9..59eeb0df2 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/block/WorldRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/block/WorldRenderer.kt @@ -23,20 +23,26 @@ import de.bixilon.minosoft.gui.rendering.Renderer import de.bixilon.minosoft.gui.rendering.RendererBuilder 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.CullSectionPreparer import de.bixilon.minosoft.gui.rendering.block.preparer.GenericSectionPreparer -import de.bixilon.minosoft.gui.rendering.block.preparer.GreedySectionPreparer +import de.bixilon.minosoft.gui.rendering.input.camera.Frustum +import de.bixilon.minosoft.gui.rendering.modding.events.FrustumChangeEvent import de.bixilon.minosoft.gui.rendering.models.ModelLoader import de.bixilon.minosoft.gui.rendering.system.base.RenderSystem import de.bixilon.minosoft.gui.rendering.system.base.phases.OpaqueDrawable import de.bixilon.minosoft.gui.rendering.system.base.phases.TranslucentDrawable import de.bixilon.minosoft.gui.rendering.system.base.phases.TransparentDrawable +import de.bixilon.minosoft.modding.event.events.ChunkDataChangeEvent +import de.bixilon.minosoft.modding.event.events.ChunkUnloadEvent +import de.bixilon.minosoft.modding.event.invoker.CallbackEventInvoker import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection +import de.bixilon.minosoft.util.KUtil.synchronizedMapOf import de.bixilon.minosoft.util.KUtil.toResourceLocation +import de.bixilon.minosoft.util.KUtil.toSynchronizedMap +import de.bixilon.minosoft.util.collections.SynchronizedMap +import glm_.vec2.Vec2i import java.io.FileInputStream import java.util.zip.GZIPInputStream import java.util.zip.ZipInputStream -import kotlin.random.Random class WorldRenderer( private val connection: PlayConnection, @@ -48,11 +54,14 @@ class WorldRenderer( private val world: World = connection.world private val sectionPreparer: AbstractSectionPreparer = GenericSectionPreparer(renderWindow) private val lightMap = LightMap(connection) - private lateinit var mesh: ChunkSectionMeshes + private val meshes: SynchronizedMap> = synchronizedMapOf() + private var visibleMeshes: MutableSet = mutableSetOf() // ToDo: Split in opaque, transparent, translucent meshes - private val culledPreparer = GenericSectionPreparer(renderWindow, CullSectionPreparer(renderWindow)) - private val greedyPreparer = GenericSectionPreparer(renderWindow, GreedySectionPreparer(renderWindow)) + val visibleSize: Int + get() = visibleMeshes.size + val preparedSize: Int + get() = visibleMeshes.size override fun init() { val asset = Resources.getAssetVersionByVersion(connection.version) @@ -75,37 +84,49 @@ class WorldRenderer( renderWindow.textureManager.staticTextures.animator.use(transparentShader) lightMap.use(transparentShader) + connection.registerEvent(CallbackEventInvoker.of { onFrustumChange(it.frustum) }) - val random = Random(0L) - val blockState1 = connection.registries.blockRegistry["grass_block"]?.defaultState - val blockState2 = connection.registries.blockRegistry["diamond_block"]!!.defaultState//.withProperties(BlockProperties.MULTIPART_SOUTH to MultipartDirectionParser.SIDE, BlockProperties.MULTIPART_NORTH to MultipartDirectionParser.SIDE, BlockProperties.MULTIPART_EAST to MultipartDirectionParser.SIDE, BlockProperties.MULTIPART_WEST to MultipartDirectionParser.SIDE) - val section = ChunkSection(Array(4096) { - when (random.nextInt(3)) { - 1 -> blockState1 - 2 -> blockState2 - else -> null + + connection.registerEvent(CallbackEventInvoker.of { + val sections = it.chunk.sections ?: return@of + for ((sectionHeight, section) in sections) { + prepareSection(it.chunkPosition, sectionHeight, section) } }) - //val section = ChunkSection(Array(4096) { if (it < 1) blockState else null }) - mesh = sectionPreparer.prepare(section) + connection.registerEvent(CallbackEventInvoker.of { + val meshes = this.meshes.remove(it.chunkPosition)?.values ?: return@of - for (i in 0 until 1000) - mesh = sectionPreparer.prepare(section) + renderWindow.queue += { + for (mesh in meshes) { + mesh.unload() + this.visibleMeshes -= mesh + } + } + }) + } - /* - Log.log(LogMessageType.OTHER, LogLevels.WARN){"Culling now..."} - - val culledMesh = culledPreparer.prepare(section) - for (i in 0 until 1000){ - culledPreparer.prepare(section) + @Synchronized + private fun prepareSection(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection? = world[chunkPosition]?.sections?.get(sectionHeight)) { + if (section == null) { + return } - val greedyMesh = greedyPreparer.prepare(section) - Log.log(LogMessageType.OTHER,LogLevels.INFO){"Culling has ${culledMesh.data.size / ChunkSectionMesh.SectionArrayMeshStruct.FLOATS_PER_VERTEX}, greedy meshed has ${greedyMesh.data.size / ChunkSectionMesh.SectionArrayMeshStruct.FLOATS_PER_VERTEX}."} + val mesh = sectionPreparer.prepare(chunkPosition, sectionHeight, section, arrayOfNulls(6)) - */ - mesh.load() + val meshes = this.meshes.getOrPut(chunkPosition) { synchronizedMapOf() } + val currentMesh = meshes.remove(sectionHeight) + + renderWindow.queue += { + if (currentMesh != null) { + currentMesh.unload() + this.visibleMeshes -= currentMesh + } + + mesh.load() + meshes[sectionHeight] = mesh + this.visibleMeshes += mesh + } } override fun setupOpaque() { @@ -114,7 +135,9 @@ class WorldRenderer( } override fun drawOpaque() { - mesh.opaqueMesh?.draw() + for (mesh in visibleMeshes) { + mesh.opaqueMesh?.draw() + } } override fun setupTranslucent() { @@ -123,7 +146,9 @@ class WorldRenderer( } override fun drawTranslucent() { - mesh.translucentMesh?.draw() + for (mesh in visibleMeshes) { + mesh.translucentMesh?.draw() + } } override fun setupTransparent() { @@ -132,7 +157,25 @@ class WorldRenderer( } override fun drawTransparent() { - mesh.transparentMesh?.draw() + for (mesh in visibleMeshes) { + mesh.transparentMesh?.draw() + } + } + + private fun onFrustumChange(frustum: Frustum) { + val visible: MutableSet = mutableSetOf() + + // ToDo + for ((chunkPosition, meshes) in this.meshes.toSynchronizedMap()) { + for ((sectionHeight, mesh) in meshes) { + if (!frustum.containsChunk(chunkPosition, sectionHeight, mesh.minPosition, mesh.maxPosition)) { + continue + } + visible += mesh + } + } + + this.visibleMeshes = visible } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/block/mesh/ChunkSectionMeshes.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/block/mesh/ChunkSectionMeshes.kt index 24646b7fe..68d8c1fe4 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/block/mesh/ChunkSectionMeshes.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/block/mesh/ChunkSectionMeshes.kt @@ -55,6 +55,13 @@ class ChunkSectionMeshes( } } + @Synchronized + fun unload() { + opaqueMesh?.unload() + translucentMesh?.unload() + transparentMesh?.unload() + } + fun addBlock(x: Int, y: Int, z: Int) { if (x < minPosition.x) { minPosition.x = x diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/AbstractSectionPreparer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/AbstractSectionPreparer.kt index bb76578ab..bd70aadab 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/AbstractSectionPreparer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/AbstractSectionPreparer.kt @@ -2,9 +2,10 @@ package de.bixilon.minosoft.gui.rendering.block.preparer import de.bixilon.minosoft.data.world.ChunkSection import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMeshes +import glm_.vec2.Vec2i interface AbstractSectionPreparer { - fun prepare(section: ChunkSection): ChunkSectionMeshes + fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array): ChunkSectionMeshes } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/CullSectionPreparer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/CullSectionPreparer.kt index 85bc1fd5f..5070a60e8 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/CullSectionPreparer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/CullSectionPreparer.kt @@ -7,6 +7,7 @@ import de.bixilon.minosoft.gui.rendering.RenderWindow import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMeshes import de.bixilon.minosoft.gui.rendering.util.VecUtil import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition +import glm_.vec2.Vec2i import glm_.vec3.Vec3i import java.util.* @@ -14,53 +15,61 @@ class CullSectionPreparer( val renderWindow: RenderWindow, ) : AbstractSectionPreparer { - override fun prepare(section: ChunkSection): ChunkSectionMeshes { + override fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array): ChunkSectionMeshes { val mesh = ChunkSectionMeshes(renderWindow) val random = Random(0L) + val blocks = section.blocks var block: BlockState? - val neighbours: Array = arrayOfNulls(Directions.SIZE) + val neighbourBlocks: Array = arrayOfNulls(Directions.SIZE) + + val offsetX = chunkPosition.x * ProtocolDefinition.SECTION_WIDTH_X + val offsetY = sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y + val offsetZ = chunkPosition.y * ProtocolDefinition.SECTION_WIDTH_Z for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) { for (y in 0 until ProtocolDefinition.SECTION_HEIGHT_Y) { for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { - block = section.blocks[ChunkSection.getIndex(x, y, z)] + block = blocks[ChunkSection.getIndex(x, y, z)] val model = block?.model ?: continue // ToDo: Chunk borders - neighbours[Directions.DOWN.ordinal] = if (y == 0) { - null + neighbourBlocks[Directions.DOWN.ordinal] = if (y == 0) { + neighbours[Directions.DOWN.ordinal]?.blocks?.get(ChunkSection.getIndex(x, ProtocolDefinition.SECTION_MAX_Y, z)) } else { - section.blocks[ChunkSection.getIndex(x, y - 1, z)] + blocks[ChunkSection.getIndex(x, y - 1, z)] } - neighbours[Directions.UP.ordinal] = if (y == ProtocolDefinition.SECTION_MAX_Y) { - null + neighbourBlocks[Directions.UP.ordinal] = if (y == ProtocolDefinition.SECTION_MAX_Y) { + neighbours[Directions.UP.ordinal]?.blocks?.get(ChunkSection.getIndex(x, 0, z)) } else { - section.blocks[ChunkSection.getIndex(x, y + 1, z)] - } - neighbours[Directions.NORTH.ordinal] = if (z == 0) { - null - } else { - section.blocks[ChunkSection.getIndex(x, y, z - 1)] - } - neighbours[Directions.SOUTH.ordinal] = if (z == ProtocolDefinition.SECTION_MAX_Z) { - null - } else { - section.blocks[ChunkSection.getIndex(x, y, z + 1)] - } - neighbours[Directions.WEST.ordinal] = if (x == 0) { - null - } else { - section.blocks[ChunkSection.getIndex(x - 1, y, z)] - } - neighbours[Directions.EAST.ordinal] = if (x == ProtocolDefinition.SECTION_MAX_X) { - null - } else { - section.blocks[ChunkSection.getIndex(x + 1, y, z)] + blocks[ChunkSection.getIndex(x, y + 1, z)] } - random.setSeed(VecUtil.generatePositionHash(x, y, z)) - val rendered = model.singleRender(Vec3i(x, y, z), mesh, random, neighbours, 0xFF, floatArrayOf(1.0f, 1.0f, 1.0f, 1.0f)) + neighbourBlocks[Directions.NORTH.ordinal] = if (z == 0) { + neighbours[Directions.NORTH.ordinal]?.blocks?.get(ChunkSection.getIndex(x, y, ProtocolDefinition.SECTION_MAX_Z)) + } else { + blocks[ChunkSection.getIndex(x, y, z - 1)] + } + neighbourBlocks[Directions.SOUTH.ordinal] = if (z == ProtocolDefinition.SECTION_MAX_Z) { + neighbours[Directions.NORTH.ordinal]?.blocks?.get(ChunkSection.getIndex(x, y, 0)) + } else { + blocks[ChunkSection.getIndex(x, y, z + 1)] + } + + neighbourBlocks[Directions.WEST.ordinal] = if (x == 0) { + neighbours[Directions.WEST.ordinal]?.blocks?.get(ChunkSection.getIndex(ProtocolDefinition.SECTION_MAX_X, y, z)) + } else { + blocks[ChunkSection.getIndex(x - 1, y, z)] + } + neighbourBlocks[Directions.EAST.ordinal] = if (x == ProtocolDefinition.SECTION_MAX_X) { + neighbours[Directions.WEST.ordinal]?.blocks?.get(ChunkSection.getIndex(0, y, z)) + } else { + blocks[ChunkSection.getIndex(x + 1, y, z)] + } + + val position = Vec3i(offsetX + x, offsetY + y, offsetZ + z) + random.setSeed(VecUtil.generatePositionHash(position.x, position.y, position.z)) + val rendered = model.singleRender(position, mesh, random, neighbourBlocks, 0xFF, floatArrayOf(1.0f, 1.0f, 1.0f, 1.0f)) if (rendered) { mesh.addBlock(x, y, z) } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/GenericSectionPreparer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/GenericSectionPreparer.kt index d0b4a86f5..989de2dec 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/GenericSectionPreparer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/GenericSectionPreparer.kt @@ -19,6 +19,7 @@ import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMeshes import de.bixilon.minosoft.util.logging.Log import de.bixilon.minosoft.util.logging.LogLevels import de.bixilon.minosoft.util.logging.LogMessageType +import glm_.vec2.Vec2i class GenericSectionPreparer( @@ -26,10 +27,10 @@ class GenericSectionPreparer( private val preparer: AbstractSectionPreparer = CullSectionPreparer(renderWindow), ) : AbstractSectionPreparer { - override fun prepare(section: ChunkSection): ChunkSectionMeshes { + override fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array): ChunkSectionMeshes { val startTime = System.nanoTime() - val mesh = preparer.prepare(section) + val mesh = preparer.prepare(chunkPosition, sectionHeight, section, neighbours) val time = System.nanoTime() val delta = time - startTime diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/GreedySectionPreparer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/GreedySectionPreparer.kt index 96e84e062..d0a9f23c5 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/GreedySectionPreparer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/block/preparer/GreedySectionPreparer.kt @@ -22,6 +22,7 @@ import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMeshes import de.bixilon.minosoft.gui.rendering.models.baked.block.GreedyBakedBlockModel import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition.SECTION_SIZE import de.bixilon.minosoft.util.KUtil.decide +import glm_.vec2.Vec2i import glm_.vec3.Vec3i import java.util.* @@ -36,7 +37,7 @@ class GreedySectionPreparer( // base taken from https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/ - override fun prepare(section: ChunkSection): ChunkSectionMeshes { + override fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array): ChunkSectionMeshes { val mesh = ChunkSectionMesh(renderWindow) val random = Random(0L) 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 2e80524f1..f347565e5 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 @@ -87,7 +87,7 @@ class DebugHUDElement(hudRenderer: HUDRenderer) : LayoutedHUDElement layout += TextElement(hudRenderer, TextComponent(RunConfiguration.VERSION_STRING, ChatColors.RED)) layout += AutoTextElement(hudRenderer, 1) { "FPS ${renderWindow.renderStats.smoothAvgFPS.round10}" } renderWindow[WorldRenderer]?.apply { - // ToDo: layout += AutoTextElement(hudRenderer, 1) { "C v=${visibleChunks.size}, p=${allChunkSections.size}, q=${queuedChunks.size}, t=${connection.world.chunks.size}" } + layout += AutoTextElement(hudRenderer, 1) { "C v=${visibleSize}, p=${preparedSize}, q=-1, t=${connection.world.chunks.size}" } } layout += AutoTextElement(hudRenderer, 1) { "E t=${connection.world.entities.size}" } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/input/camera/Frustum.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/input/camera/Frustum.kt index 30a5f02cb..fcb5664bd 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/input/camera/Frustum.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/input/camera/Frustum.kt @@ -17,8 +17,6 @@ package de.bixilon.minosoft.gui.rendering.input.camera import de.bixilon.minosoft.data.registries.AABB import de.bixilon.minosoft.gui.rendering.RenderConstants import de.bixilon.minosoft.gui.rendering.util.VecUtil.of -import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.EMPTY -import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.util.KUtil import de.bixilon.minosoft.util.KUtil.get import de.bixilon.minosoft.util.enum.ValuesEnum @@ -163,11 +161,10 @@ class Frustum(private val camera: Camera) { return true } - fun containsChunk(chunkPosition: Vec2i, lowestBlockHeight: Int, highestBlockHeight: Int): Boolean { - val from = Vec3i.of(chunkPosition, 0, Vec3i.EMPTY) - from.y = lowestBlockHeight - val to = Vec3(from.x + ProtocolDefinition.SECTION_WIDTH_X, highestBlockHeight, from.z + ProtocolDefinition.SECTION_WIDTH_Z) - return containsRegion(Vec3(from), to) + fun containsChunk(chunkPosition: Vec2i, sectionHeight: Int, minPosition: Vec3i, maxPosition: Vec3i): Boolean { + val from = Vec3i.of(chunkPosition, sectionHeight, minPosition) + val to = Vec3i.of(chunkPosition, sectionHeight, maxPosition) + return containsRegion(Vec3(from), Vec3(to)) } fun containsAABB(aabb: AABB): Boolean {