diff --git a/src/main/java/de/bixilon/minosoft/data/world/BlockPosition.kt b/src/main/java/de/bixilon/minosoft/data/world/BlockPosition.kt index f1644d742..398558d1a 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/BlockPosition.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/BlockPosition.kt @@ -17,7 +17,17 @@ import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition data class BlockPosition(val x: Int, val y: Int, val z: Int) { fun getChunkLocation(): ChunkLocation { - return ChunkLocation(this.x / ProtocolDefinition.SECTION_WIDTH_X, this.z / ProtocolDefinition.SECTION_WIDTH_Z) + val chunkX = if (this.x >= 0) { + this.x / ProtocolDefinition.SECTION_WIDTH_X + } else { + ((this.x + 1) / ProtocolDefinition.SECTION_WIDTH_X) - 1 + } + val chunkY = if (this.z >= 0) { + this.z / ProtocolDefinition.SECTION_WIDTH_Z + } else { + ((this.z + 1) / ProtocolDefinition.SECTION_WIDTH_Z) - 1 + } + return ChunkLocation(chunkX, chunkY) } fun getInChunkLocation(): InChunkLocation { @@ -32,6 +42,10 @@ data class BlockPosition(val x: Int, val y: Int, val z: Int) { return InChunkLocation(x, this.y, z) } + fun getSectionHeight(): Int { + return y / ProtocolDefinition.SECTION_HEIGHT_Y + } + override fun toString(): String { return "($x $y $z)" } diff --git a/src/main/java/de/bixilon/minosoft/data/world/Chunk.java b/src/main/java/de/bixilon/minosoft/data/world/Chunk.java index 310326862..e6e661e5a 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/Chunk.java +++ b/src/main/java/de/bixilon/minosoft/data/world/Chunk.java @@ -87,4 +87,13 @@ public class Chunk { public HashMap getSections() { return this.sections; } + + public ChunkSection getSectionOrCreate(int sectionHeight) { + ChunkSection section = this.sections.get(sectionHeight); + if (section == null) { + section = new ChunkSection(); + this.sections.put(sectionHeight, section); + } + return section; + } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/InChunkLocation.kt b/src/main/java/de/bixilon/minosoft/data/world/InChunkLocation.kt index bcb3270b7..90f5e5fb2 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/InChunkLocation.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/InChunkLocation.kt @@ -20,6 +20,10 @@ data class InChunkLocation(val x: Int, val y: Int, val z: Int) { return InChunkSectionLocation(x, y % ProtocolDefinition.SECTION_HEIGHT_Y, z) } + fun getSectionHeight(): Int { + return y / ProtocolDefinition.SECTION_HEIGHT_Y + } + override fun toString(): String { return "($x $y $z)" } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/ChunkPreparer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/ChunkPreparer.kt index 60f65c559..a3e81203d 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/ChunkPreparer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/ChunkPreparer.kt @@ -106,6 +106,7 @@ object ChunkPreparer { "minecraft:bedrock" -> 0 "minecraft:dirt" -> 1 "minecraft:stone" -> 2 + "minecraft:glass" -> 3 else -> 2 } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/Mesh.java b/src/main/java/de/bixilon/minosoft/gui/rendering/Mesh.java index bbf300f2b..2aa8794a4 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/Mesh.java +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/Mesh.java @@ -8,9 +8,9 @@ import org.lwjgl.opengl.GL20; import static org.lwjgl.opengl.GL11.GL_TRIANGLES; import static org.lwjgl.opengl.GL11.glDrawArrays; +import static org.lwjgl.opengl.GL15.glDeleteBuffers; import static org.lwjgl.opengl.GL15.glGenBuffers; -import static org.lwjgl.opengl.GL30.glBindVertexArray; -import static org.lwjgl.opengl.GL30.glGenVertexArrays; +import static org.lwjgl.opengl.GL30.*; public class Mesh { int vAO; @@ -44,4 +44,9 @@ public class Mesh { glBindVertexArray(this.vAO); glDrawArrays(GL_TRIANGLES, 0, this.trianglesCount); } + + public void unload() { + glDeleteVertexArrays(this.vAO); + glDeleteBuffers(this.vBO); + } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt index f4f832d65..a940ed89d 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt @@ -1,5 +1,6 @@ package de.bixilon.minosoft.gui.rendering +import de.bixilon.minosoft.data.world.ChunkLocation import de.bixilon.minosoft.protocol.network.Connection import org.lwjgl.* import org.lwjgl.glfw.Callbacks @@ -10,6 +11,7 @@ import org.lwjgl.opengl.* import org.lwjgl.opengl.GL11.glClear import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryUtil +import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentLinkedQueue class RenderWindow(private val connection: Connection) { @@ -24,9 +26,10 @@ class RenderWindow(private val connection: Connection) { private var lastFrame = 0.0 lateinit var camera: Camera - val meshesToDraw = ConcurrentLinkedQueue() val renderQueue = ConcurrentLinkedQueue() + val chunkSectionsToDraw = ConcurrentHashMap>() + fun init() { // Setup an error callback. The default implementation @@ -108,11 +111,7 @@ class RenderWindow(private val connection: Connection) { } fun startLoop() { - // val world = World() - // world.setChunk(ChunkLocation(0, 0), DummyData.getDummyChunk()) - // world.setChunk(ChunkLocation(1, 0), DummyData.getDummyChunk()) - - texture0 = TextureArray(arrayOf("/textures/bedrock.png", "/textures/dirt.png", "/textures/stone.png")) + texture0 = TextureArray(arrayOf("/textures/bedrock.png", "/textures/dirt.png", "/textures/stone.png", "/textures/glass.png")) texture0.load() shader = Shader("vertex.glsl", "fragment.glsl") @@ -124,13 +123,6 @@ class RenderWindow(private val connection: Connection) { camera.calculateViewMatrix(shader) - // for ((chunkLocation, chunk) in world.allChunks) { - // for ((sectionHeight, section) in chunk.sections) { - // meshesToDraw.add(Mesh(ChunkPreparer.prepareChunk(world, chunkLocation, sectionHeight, section), chunkLocation, sectionHeight)) - // } - // } - - var framesLastSecond = 0 var lastCalcTime = glfwGetTime() while (!glfwWindowShouldClose(windowId)) { @@ -147,8 +139,10 @@ class RenderWindow(private val connection: Connection) { camera.calculateViewMatrix(shader) - for (mesh in meshesToDraw) { - mesh.draw(shader) + for ((_, map) in chunkSectionsToDraw) { + for ((_, mesh) in map) { + mesh.draw(shader) + } } glfwSwapBuffers(windowId) // swap the color buffers @@ -157,17 +151,20 @@ class RenderWindow(private val connection: Connection) { // invoked during this call. glfwPollEvents() handleInput() - camera.handleInput(deltaTime) - if (glfwGetTime() - lastCalcTime >= 1.0) { - glfwSetWindowTitle(windowId, "FPS: $framesLastSecond") - lastCalcTime = glfwGetTime() - framesLastSecond = 0 - } - framesLastSecond++ + for (renderQueueElement in renderQueue) { renderQueueElement.run() renderQueue.remove(renderQueueElement) } + + camera.handleInput(deltaTime) + + if (glfwGetTime() - lastCalcTime >= 0.5) { + glfwSetWindowTitle(windowId, "FPS: ${framesLastSecond * 2}") + lastCalcTime = glfwGetTime() + framesLastSecond = 0 + } + framesLastSecond++ } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/Renderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/Renderer.kt index 48f07de3c..1e46ccb39 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/Renderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/Renderer.kt @@ -7,8 +7,10 @@ import de.bixilon.minosoft.data.world.ChunkSection import de.bixilon.minosoft.gui.rendering.ChunkPreparer.prepareChunk import de.bixilon.minosoft.protocol.network.Connection import de.bixilon.minosoft.util.Util +import de.bixilon.minosoft.util.logging.Log import glm_.vec3.Vec3 import org.lwjgl.Version +import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -18,7 +20,7 @@ class Renderer(private val connection: Connection) { fun start() { Thread({ - println("Hello LWJGL " + Version.getVersion() + "!") + Log.info("Hello LWJGL " + Version.getVersion() + "!") renderWindow.init() renderWindow.startLoop() renderWindow.exit() @@ -26,6 +28,7 @@ class Renderer(private val connection: Connection) { } fun prepareChunk(chunkLocation: ChunkLocation, chunk: Chunk) { + renderWindow.chunkSectionsToDraw[chunkLocation] = ConcurrentHashMap() for ((sectionHeight, section) in chunk.sections) { prepareChunkSection(chunkLocation, sectionHeight, section) } @@ -34,8 +37,23 @@ class Renderer(private val connection: Connection) { fun prepareChunkSection(chunkLocation: ChunkLocation, sectionHeight: Int, section: ChunkSection) { executor.execute { val data = prepareChunk(connection.player.world, chunkLocation, sectionHeight, section) + val sectionMap = renderWindow.chunkSectionsToDraw[chunkLocation]!! renderWindow.renderQueue.add { - renderWindow.meshesToDraw.add(Mesh(data, chunkLocation, sectionHeight)) + sectionMap[sectionHeight]?.unload() + sectionMap.remove(sectionHeight) + sectionMap[sectionHeight] = Mesh(data, chunkLocation, sectionHeight) + } + } + } + + fun clearCache() { + renderWindow.renderQueue.add { + for ((location, map) in renderWindow.chunkSectionsToDraw) { + for ((sectionHeight, mesh) in map) { + mesh.unload() + map.remove(sectionHeight) + } + renderWindow.chunkSectionsToDraw.remove(location) } } } @@ -43,7 +61,6 @@ class Renderer(private val connection: Connection) { fun teleport(position: Location) { renderWindow.renderQueue.add { renderWindow.camera.setPosition(Vec3(position.x, position.y, position.z)) - } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java b/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java index dde7e6e84..741b56e0b 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java +++ b/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java @@ -14,6 +14,7 @@ package de.bixilon.minosoft.protocol.network; import de.bixilon.minosoft.Minosoft; +import de.bixilon.minosoft.config.StaticConfiguration; import de.bixilon.minosoft.data.Player; import de.bixilon.minosoft.data.VelocityHandler; import de.bixilon.minosoft.data.commands.CommandRootNode; @@ -57,6 +58,7 @@ public class Connection { private final Player player; private final String hostname; private final Recipes recipes = new Recipes(); + private final Renderer renderer = new Renderer(this); private LinkedList addresses; private int desiredVersionNumber = -1; private ServerAddress address; @@ -71,7 +73,6 @@ public class Connection { private CommandRootNode commandRootNode; private ConnectionPing connectionStatusPing; private ServerListPongEvent pong; - private final Renderer renderer = new Renderer(this); public Connection(int connectionId, String hostname, Player player) { this.connectionId = connectionId; @@ -369,7 +370,9 @@ public class Connection { case FAILED_NO_RETRY -> handlePingCallbacks(null); case PLAY -> { Minosoft.CONNECTIONS.put(getConnectionId(), this); - this.renderer.start(); + if (!StaticConfiguration.HEADLESS_MODE) { + this.renderer.start(); + } if (CLI.getCurrentConnection() == null) { CLI.setCurrentConnection(this); diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.java index 7fa9e41ae..ff8665bda 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.java +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.java @@ -17,6 +17,7 @@ import de.bixilon.minosoft.data.mappings.blocks.Block; import de.bixilon.minosoft.data.mappings.tweaker.VersionTweaker; import de.bixilon.minosoft.data.world.BlockPosition; import de.bixilon.minosoft.data.world.Chunk; +import de.bixilon.minosoft.data.world.ChunkSection; import de.bixilon.minosoft.modding.event.events.BlockChangeEvent; import de.bixilon.minosoft.protocol.network.Connection; import de.bixilon.minosoft.protocol.packets.ClientboundPacket; @@ -51,14 +52,18 @@ public class PacketBlockChange extends ClientboundPacket { } connection.fireEvent(new BlockChangeEvent(connection, this)); + int sectionHeight = getPosition().getSectionHeight(); + ChunkSection section = chunk.getSectionOrCreate(sectionHeight); // tweak if (!connection.getVersion().isFlattened()) { Block block = VersionTweaker.transformBlock(getBlock(), chunk, getPosition().getInChunkLocation()); - chunk.setBlock(getPosition().getInChunkLocation(), block); + section.setBlock(getPosition().getInChunkLocation().getInChunkSectionLocation(), block); } else { - chunk.setBlock(getPosition().getInChunkLocation(), getBlock()); + section.setBlock(getPosition().getInChunkLocation().getInChunkSectionLocation(), getBlock()); } + + connection.getRenderer().prepareChunkSection(getPosition().getChunkLocation(), sectionHeight, section); } @Override diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkBulk.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkBulk.java index bb14b8c5b..31ff57277 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkBulk.java +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkBulk.java @@ -84,6 +84,8 @@ public class PacketChunkBulk extends ClientboundPacket { getChunks().forEach(((location, chunk) -> connection.fireEvent(new ChunkDataChangeEvent(connection, location, chunk)))); connection.getPlayer().getWorld().setChunks(getChunks()); + + getChunks().forEach(((location, chunk) -> connection.getRenderer().prepareChunk(location, chunk))); } @Override diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.java index 50424989e..8a4f04df1 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.java +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.java @@ -17,6 +17,7 @@ import de.bixilon.minosoft.data.mappings.blocks.Block; import de.bixilon.minosoft.data.mappings.tweaker.VersionTweaker; import de.bixilon.minosoft.data.world.Chunk; import de.bixilon.minosoft.data.world.ChunkLocation; +import de.bixilon.minosoft.data.world.ChunkSection; import de.bixilon.minosoft.data.world.InChunkLocation; import de.bixilon.minosoft.modding.event.events.MultiBlockChangeEvent; import de.bixilon.minosoft.protocol.network.Connection; @@ -25,6 +26,7 @@ import de.bixilon.minosoft.protocol.protocol.InByteBuffer; import de.bixilon.minosoft.util.logging.Log; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import static de.bixilon.minosoft.protocol.protocol.ProtocolVersions.*; @@ -99,6 +101,17 @@ public class PacketMultiBlockChange extends ClientboundPacket { chunk.setBlock(entry.getKey(), block); } } + + HashSet sectionHeights = new HashSet<>(); + + for (var entry : this.blocks.entrySet()) { + sectionHeights.add(entry.getKey().getSectionHeight()); + } + + for (var sectionHeight : sectionHeights) { + ChunkSection section = chunk.getSectionOrCreate(sectionHeight); + connection.getRenderer().prepareChunkSection(getLocation(), sectionHeight, section); + } } @Override diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketRespawn.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketRespawn.java index 6b04470ef..57d209134 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketRespawn.java +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketRespawn.java @@ -89,6 +89,8 @@ public class PacketRespawn extends ClientboundPacket { connection.getPlayer().getWorld().setDimension(getDimension()); connection.getPlayer().setSpawnConfirmed(false); connection.getPlayer().setGameMode(getGameMode()); + + connection.getRenderer().clearCache(); } @Override