diff --git a/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.java b/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.java index 8469c7512..2426f8ab8 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.java +++ b/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.java @@ -43,6 +43,9 @@ public abstract class ChatComponent { if (raw == null) { return new BaseComponent(); } + if (raw instanceof ChatComponent component) { + return component; + } if (raw instanceof JsonPrimitive primitive) { raw = primitive.getAsString(); } 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 df2fbbd7b..0c34498c8 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt @@ -4,9 +4,11 @@ import de.bixilon.minosoft.data.entities.EntityRotation import de.bixilon.minosoft.data.entities.Location import de.bixilon.minosoft.gui.rendering.chunk.ChunkRenderer import de.bixilon.minosoft.gui.rendering.hud.HUDRenderer +import de.bixilon.minosoft.gui.rendering.hud.elements.RenderStats import de.bixilon.minosoft.protocol.network.Connection import de.bixilon.minosoft.protocol.packets.serverbound.play.PacketPlayerPositionAndRotationSending import de.bixilon.minosoft.util.CountUpAndDownLatch +import de.bixilon.minosoft.util.logging.Log import org.lwjgl.* import org.lwjgl.glfw.Callbacks import org.lwjgl.glfw.GLFW.* @@ -17,26 +19,27 @@ import org.lwjgl.opengl.GL11.* import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryUtil import java.util.concurrent.ConcurrentLinkedQueue -import kotlin.math.roundToInt class RenderWindow(private val connection: Connection, val rendering: Rendering) { - private var screenWidth = 800 - private var screenHeight = 600 + val renderStats = RenderStats() + var screenWidth = 800 + var screenHeight = 600 private var polygonEnabled = false private var windowId: Long = 0 private var deltaTime = 0.0 // time between current frame and last frame private var lastFrame = 0.0 lateinit var camera: Camera + var latch = CountUpAndDownLatch(1) // all renderers - val chunkRenderer: ChunkRenderer = ChunkRenderer(connection.player.world, this) + val chunkRenderer: ChunkRenderer = ChunkRenderer(connection, connection.player.world, this) val hudRenderer: HUDRenderer = HUDRenderer(connection, this) val renderQueue = ConcurrentLinkedQueue() - fun init(latch: CountUpAndDownLatch? = null) { + fun init(latch: CountUpAndDownLatch) { // Setup an error callback. The default implementation // will print the error message in System.err. GLFWErrorCallback.createPrint(System.err).set() @@ -107,9 +110,9 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - chunkRenderer.init(connection) + chunkRenderer.init() - hudRenderer.init(connection) + hudRenderer.init() glfwSetWindowSizeCallback(windowId, object : GLFWWindowSizeCallback() { @@ -128,23 +131,22 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering) camera.calculateProjectionMatrix(screenWidth, screenHeight, chunkRenderer.chunkShader) camera.calculateViewMatrix(chunkRenderer.chunkShader) - glfwShowWindow(windowId) - latch?.countDown() + Log.debug("Rendering is prepared and ready to go!") + latch.countDown() + latch.waitUntilZero() + this.latch.waitUntilZero() + glfwShowWindow(windowId) } fun startRenderLoop() { - var framesLastSecond = 0 - var lastCalcTime = glfwGetTime() - var frameTimeLastCalc = 0.0 - var lastPositionChangeTime = 0.0 while (!glfwWindowShouldClose(windowId)) { + renderStats.startFrame() glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) // clear the framebuffer val currentFrame = glfwGetTime() - deltaTime = currentFrame - lastFrame lastFrame = currentFrame @@ -153,26 +155,15 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering) chunkRenderer.draw() - hudRenderer.draw() + renderStats.endDraw() + glfwSwapBuffers(windowId) - glfwPollEvents() - camera.handleInput(deltaTime) - frameTimeLastCalc += glfwGetTime() - currentFrame - - if (glfwGetTime() - lastCalcTime >= 0.25) { - glfwSetWindowTitle(windowId, "Minosoft | FPS: ${framesLastSecond * 4} (${(0.25 * framesLastSecond / (frameTimeLastCalc)).roundToInt()})") - hudRenderer.fps = framesLastSecond * 4 - lastCalcTime = glfwGetTime() - framesLastSecond = 0 - frameTimeLastCalc = 0.0 - } - framesLastSecond++ if (glfwGetTime() - lastPositionChangeTime > 0.05) { // ToDo: Replace this with proper movement and only send it, when our position changed @@ -181,10 +172,13 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering) } + // handle opengl context tasks for (renderQueueElement in renderQueue) { renderQueueElement.run() renderQueue.remove(renderQueueElement) } + + renderStats.endFrame() } } 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 1c2d095f4..f147d729f 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/Renderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/Renderer.kt @@ -1,10 +1,7 @@ package de.bixilon.minosoft.gui.rendering -import de.bixilon.minosoft.protocol.network.Connection - interface Renderer { - - fun init(connection: Connection) - + fun init() fun draw() + fun screenChangeResizeCallback(width: Int, height: Int) {} } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/Rendering.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/Rendering.kt index ca8f3b244..3df30b3f5 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/Rendering.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/Rendering.kt @@ -13,10 +13,10 @@ import java.util.concurrent.Executors class Rendering(private val connection: Connection) { val renderWindow: RenderWindow = RenderWindow(connection, this) - val latch = CountUpAndDownLatch(1) val executor: ExecutorService = Executors.newFixedThreadPool(4, Util.getThreadFactory(String.format("Rendering#%d", connection.connectionId))) - fun start() { + fun start(latch: CountUpAndDownLatch) { + latch.countUp() Thread({ Log.info("Hello LWJGL " + Version.getVersion() + "!") renderWindow.init(latch) @@ -27,6 +27,10 @@ class Rendering(private val connection: Connection) { fun teleport(position: Location) { + // tell the window we are ready (received position) + if (renderWindow.latch.count > 0) { + renderWindow.latch.countDown() + } renderWindow.renderQueue.add { renderWindow.camera.cameraPosition = Vec3(position.x, position.y, position.z) } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt index 674d9fed0..d30c4623d 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt @@ -19,7 +19,7 @@ import org.lwjgl.opengl.GL11.glEnable import org.lwjgl.opengl.GL13.GL_TEXTURE0 import java.util.concurrent.ConcurrentHashMap -class ChunkRenderer(private val world: World, val renderWindow: RenderWindow) : Renderer { +class ChunkRenderer(private val connection: Connection, private val world: World, val renderWindow: RenderWindow) : Renderer { private lateinit var minecraftTextures: TextureArray lateinit var chunkShader: Shader private val chunkSectionsToDraw = ConcurrentHashMap>() @@ -81,7 +81,7 @@ class ChunkRenderer(private val world: World, val renderWindow: RenderWindow) : return data.toFloatArray() } - override fun init(connection: Connection) { + override fun init() { minecraftTextures = TextureArray.createTextureArray(connection.version.assetsManager, resolveBlockTextureIds(connection.version.mapping.blockMap.values), 16, 16) // ToDo :Remove fixed size minecraftTextures.load() @@ -124,7 +124,6 @@ class ChunkRenderer(private val world: World, val renderWindow: RenderWindow) : fun prepareChunkSection(chunkLocation: ChunkLocation, sectionHeight: Int, section: ChunkSection) { renderWindow.rendering.executor.execute { - renderWindow.rendering.latch.waitUntilZero() // Wait until rendering is started try { val data = prepareChunk(chunkLocation, sectionHeight, section) val sectionMap = chunkSectionsToDraw[chunkLocation]!! diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/HUDRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/HUDRenderer.kt index 6b5ad5795..3aa4b3085 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/HUDRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/HUDRenderer.kt @@ -1,135 +1,54 @@ package de.bixilon.minosoft.gui.rendering.hud -import de.bixilon.minosoft.data.text.ChatComponent +import de.bixilon.minosoft.data.mappings.ModIdentifier import de.bixilon.minosoft.gui.rendering.RenderWindow import de.bixilon.minosoft.gui.rendering.Renderer -import de.bixilon.minosoft.gui.rendering.font.Font -import de.bixilon.minosoft.gui.rendering.font.FontBindings -import de.bixilon.minosoft.gui.rendering.shader.Shader -import de.bixilon.minosoft.gui.rendering.textures.TextureArray +import de.bixilon.minosoft.gui.rendering.hud.elements.HUDElement +import de.bixilon.minosoft.gui.rendering.hud.elements.text.HUDTextElement import de.bixilon.minosoft.protocol.network.Connection -import glm_.glm -import glm_.mat4x4.Mat4 -import glm_.vec2.Vec2 -import org.lwjgl.opengl.GL11.GL_DEPTH_TEST -import org.lwjgl.opengl.GL11.glDisable -import org.lwjgl.opengl.GL13.GL_TEXTURE0 -import java.util.concurrent.ConcurrentLinkedQueue +import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition -class HUDRenderer(private val connection: Connection, private val renderWindow: RenderWindow) : Renderer { - private val font = Font() - private val hudScale = HUDScale.MEDIUM - var fps: Int = 0 - var frame = 0 - private lateinit var fontShader: Shader - private lateinit var fontAtlasTexture: TextureArray - private lateinit var hudMeshHUD: HUDFontMesh - private var screenWidth = 0 - private var screenHeight = 0 - var chatMessages: ConcurrentLinkedQueue> = ConcurrentLinkedQueue() - private var showChat = true - private var showDebugScreen = true - private val fontBindingPerspectiveMatrices = mutableListOf(Mat4(), Mat4(), Mat4(), Mat4()) // according to FontBindings::ordinal +class HUDRenderer(private val connection: Connection, renderWindow: RenderWindow) : Renderer { + var hudScale = HUDScale.MEDIUM + val hudElements: MutableMap = mutableMapOf( + ModIdentifier("minosoft:hud_text_renderer") to HUDTextElement(connection, this, renderWindow), + ) + var lastTimePrepared = 0L - override fun init(connection: Connection) { - font.load(connection.version.assetsManager) - fontAtlasTexture = font.createAtlasTexture() - fontAtlasTexture.load() - - fontShader = Shader("font_vertex.glsl", "font_fragment.glsl") - fontShader.load() - hudMeshHUD = HUDFontMesh(floatArrayOf()) + override fun init() { + for (element in hudElements.values) { + element.init() + } } - fun drawChatComponent(position: Vec2, binding: FontBindings, text: ChatComponent, meshData: MutableList, maxSize: Vec2) { - hudMeshHUD.unload() - text.addVerticies(position, Vec2(0, 0), fontBindingPerspectiveMatrices[binding.ordinal], binding, font, hudScale, meshData, maxSize) - } - - fun screenChangeResizeCallback(width: Int, height: Int) { - fontShader.use() - screenWidth = width - screenHeight = height - - fontBindingPerspectiveMatrices[FontBindings.LEFT_UP.ordinal] = glm.ortho(0.0f, width.toFloat(), height.toFloat(), 0.0f) - fontBindingPerspectiveMatrices[FontBindings.RIGHT_UP.ordinal] = glm.ortho(width.toFloat(), 0.0f, height.toFloat(), 0.0f) - fontBindingPerspectiveMatrices[FontBindings.RIGHT_DOWN.ordinal] = glm.ortho(width.toFloat(), 0.0f, 0.0f, height.toFloat()) - fontBindingPerspectiveMatrices[FontBindings.LEFT_DOWN.ordinal] = glm.ortho(0.0f, width.toFloat(), 0.0f, height.toFloat()) - - prepare() + override fun screenChangeResizeCallback(width: Int, height: Int) { + for (element in hudElements.values) { + element.screenChangeResizeCallback(width, height) + } } override fun draw() { - fontAtlasTexture.use(GL_TEXTURE0) - - fontShader.use() - glDisable(GL_DEPTH_TEST) - - frame++ - if (frame % 15 == 0) { + if (System.currentTimeMillis() - lastTimePrepared > ProtocolDefinition.TICK_TIME) { prepare() + update() + lastTimePrepared = System.currentTimeMillis() } - hudMeshHUD.draw() + for (element in hudElements.values) { + element.draw() + } } fun prepare() { - val runtime = Runtime.getRuntime()!! - val meshData: MutableList = mutableListOf() - val componentsBindingMap: Map> = mapOf( - FontBindings.LEFT_UP to mutableListOf( - "§aMinosoft (0.1-pre1)", - ), - FontBindings.RIGHT_UP to mutableListOf(), - FontBindings.RIGHT_DOWN to mutableListOf(), - FontBindings.LEFT_DOWN to mutableListOf(), - ) - - if (showDebugScreen) { - componentsBindingMap[FontBindings.LEFT_UP]!!.addAll(listOf( - "§fFPS: §8$fps", - "§fXYZ §8${"%.4f".format(renderWindow.camera.cameraPosition.x)} / ${"%.4f".format(renderWindow.camera.cameraPosition.y)} / ${"%.4f".format(renderWindow.camera.cameraPosition.z)}", - "§fConnected to: §8${connection.address}", - )) - componentsBindingMap[FontBindings.RIGHT_UP]!!.addAll(listOf( - "§fJava: §8${Runtime.version()} ${System.getProperty("sun.arch.data.model")}bit", - "§fMemory: §8${runtime.freeMemory() * 100 / runtime.maxMemory()}% ${(runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024)}/${runtime.maxMemory() / (1024 * 1024)}MB", - "§fAllocated: §8${runtime.totalMemory() * 100 / runtime.maxMemory()}% ${runtime.totalMemory() / (1024 * 1024)}MB", - " ", - "CPU: §8${runtime.availableProcessors()}x TODO", - "OS: §8${System.getProperty("os.name")}", - " ", - "Display: §8${screenWidth}x${screenHeight}", - )) + for (element in hudElements.values) { + element.prepare() } - if (showChat) { - for (entry in chatMessages) { - if (System.currentTimeMillis() - entry.second > 10000) { - chatMessages.remove(entry) - continue - } - componentsBindingMap[FontBindings.LEFT_DOWN]!!.add(entry.first) - } - } - for ((binding, components) in componentsBindingMap) { - val offset = Vec2(3, 3) + } - if (binding == FontBindings.RIGHT_DOWN || binding == FontBindings.LEFT_DOWN) { - components.reverse() - } - - for ((_, component) in components.withIndex()) { - val currentOffset = Vec2() - val chatComponent = if (component is ChatComponent) { - component - } else { - ChatComponent.valueOf(component) - } - drawChatComponent(offset, binding, chatComponent, meshData, currentOffset) - offset += Vec2(0, currentOffset.y + 1) - } - hudMeshHUD = HUDFontMesh(meshData.toFloatArray()) + fun update() { + for (element in hudElements.values) { + element.update() } } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/HUDElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/HUDElement.kt new file mode 100644 index 000000000..9d3dc13f7 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/HUDElement.kt @@ -0,0 +1,10 @@ +package de.bixilon.minosoft.gui.rendering.hud.elements + +interface HUDElement { + + fun init() + fun prepare() + fun update() + fun draw() + fun screenChangeResizeCallback(width: Int, height: Int) {} +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/RenderStats.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/RenderStats.kt new file mode 100644 index 000000000..7e865e561 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/RenderStats.kt @@ -0,0 +1,43 @@ +package de.bixilon.minosoft.gui.rendering.hud.elements + +class RenderStats { + var fpsLastSecond = -1 + var minFrameTime = Long.MAX_VALUE + var maxFrameTime = 0L + var avgFrameTime = 0L + var frames = 0L + + private var lastFPSCalcTime = 0L + private var framesLastSecond = 0 + + private var frameStartTime = 0L + + fun startFrame() { + frameStartTime = System.nanoTime() + } + + fun endDraw() { + } + + fun endFrame() { + val frameEndTime = System.nanoTime() + val frameTime = frameEndTime - frameStartTime + if (frameTime < minFrameTime) { + minFrameTime = frameTime + } + if (frameTime > maxFrameTime) { + maxFrameTime = frameTime + } + + if (frameEndTime - lastFPSCalcTime > 1E9) { + // 1 second + fpsLastSecond = framesLastSecond + + framesLastSecond = 0 + lastFPSCalcTime = frameEndTime + } + frames++ + framesLastSecond++ + + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDChatElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDChatElement.kt new file mode 100644 index 000000000..76d7afd7a --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDChatElement.kt @@ -0,0 +1,30 @@ +package de.bixilon.minosoft.gui.rendering.hud.elements.text + +import de.bixilon.minosoft.data.text.ChatComponent +import de.bixilon.minosoft.gui.rendering.font.FontBindings +import de.bixilon.minosoft.modding.event.EventInvokerCallback +import de.bixilon.minosoft.modding.event.events.ChatMessageReceivingEvent +import java.util.concurrent.ConcurrentLinkedQueue + +class HUDChatElement(hudTextElement: HUDTextElement) : HUDText { + private var showChat = true + var chatMessages: ConcurrentLinkedQueue> = ConcurrentLinkedQueue() + + init { + hudTextElement.connection.registerEvent(EventInvokerCallback { + chatMessages.add(Pair(it.message, System.currentTimeMillis())) + }) + } + + override fun prepare(chatComponents: Map>) { + if (showChat) { + for (entry in chatMessages) { + if (System.currentTimeMillis() - entry.second > 10000) { + chatMessages.remove(entry) + continue + } + chatComponents[FontBindings.LEFT_DOWN]!!.add(entry.first) + } + } + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDDebugScreenElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDDebugScreenElement.kt new file mode 100644 index 000000000..d3873ef02 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDDebugScreenElement.kt @@ -0,0 +1,113 @@ +package de.bixilon.minosoft.gui.rendering.hud.elements.text + +import de.bixilon.minosoft.gui.rendering.font.FontBindings + +class HUDDebugScreenElement(private val hudTextElement: HUDTextElement) : HUDText { + private val runtime = Runtime.getRuntime() + + override fun prepare(chatComponents: Map>) { + chatComponents[FontBindings.LEFT_UP]!!.addAll(listOf( + "§fFPS: §8${getFPS()}", + "§fTimings: §8avg ${getAvgFrameTime()}ms, min ${getMinFrameTime()}ms, max ${getMaxFrameTime()}ms", + "§fXYZ §8${getLocation()}", + "§fConnected to: §8${hudTextElement.connection.address}", + )) + chatComponents[FontBindings.RIGHT_UP]!!.addAll(listOf( + "§fJava: §8${Runtime.version()} ${System.getProperty("sun.arch.data.model")}bit", + "§fMemory: §8${getUsedMemoryPercent()}% ${getFormattedUsedMemory()}/${getFormattedMaxMemory()}", + "§fAllocated: §8${getAllocatedMemoryPercent()}% ${getFormattedAllocatedMemory()}", + " ", + "CPU: §8${runtime.availableProcessors()}x TODO", + "OS: §8${System.getProperty("os.name")}", + " ", + "Display: §8${getScreenDimensions()}", + )) + } + + private fun nanoToMillis1d(nanos: Long): String { + return "%.1f".format(nanos / 1000000f) + } + + private fun getFPS(): String { + val renderStats = hudTextElement.renderWindow.renderStats + return "${renderStats.fpsLastSecond}" + } + + private fun getAvgFrameTime(): String { + return nanoToMillis1d(hudTextElement.renderWindow.renderStats.avgFrameTime) + } + + private fun getMinFrameTime(): String { + return nanoToMillis1d(hudTextElement.renderWindow.renderStats.minFrameTime) + } + + private fun getMaxFrameTime(): String { + return nanoToMillis1d(hudTextElement.renderWindow.renderStats.maxFrameTime) + } + + + private fun getUsedMemory(): Long { + return runtime.totalMemory() - runtime.freeMemory() + } + + private fun getFormattedUsedMemory(): String { + return formatBytes(getUsedMemory()) + } + + private fun getAllocatedMemory(): Long { + return runtime.totalMemory() + } + + private fun getFormattedAllocatedMemory(): String { + return formatBytes(getAllocatedMemory()) + } + + private fun getMaxMemory(): Long { + return runtime.maxMemory() + } + + private fun getFormattedMaxMemory(): String { + return formatBytes(getMaxMemory()) + } + + private fun getUsedMemoryPercent(): Long { + return getUsedMemory() * 100 / runtime.maxMemory() + } + + private fun getAllocatedMemoryPercent(): Long { + return getAllocatedMemory() * 100 / runtime.maxMemory() + } + + private fun getLocation(): String { + val cameraPosition = hudTextElement.renderWindow.camera.cameraPosition + return "${formatCoordinate(cameraPosition.x)} / ${formatCoordinate(cameraPosition.y)} / ${formatCoordinate(cameraPosition.z)}" + } + + private fun getScreenDimensions(): String { + return "${hudTextElement.renderWindow.screenWidth}x${hudTextElement.renderWindow.screenHeight}" + } + + + companion object { + private val UNITS = listOf("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB") + fun formatBytes(bytes: Long): String { + var lastFactor = 1L + var currentFactor = 1024L + for (unit in UNITS) { + if (bytes < currentFactor) { + if (bytes < (lastFactor * 10)) { + return "${"%.1f".format(bytes / lastFactor.toFloat())}${unit}" + } + return "${bytes / lastFactor}${unit}" + } + lastFactor = currentFactor + currentFactor *= 1024L + } + throw IllegalArgumentException() + } + + fun formatCoordinate(coordinate: Float): String { + return "%.4f".format(coordinate) + } + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/HUDFontMesh.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDFontMesh.kt similarity index 96% rename from src/main/java/de/bixilon/minosoft/gui/rendering/hud/HUDFontMesh.kt rename to src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDFontMesh.kt index 50b1a2d28..21a58d0c3 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/HUDFontMesh.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDFontMesh.kt @@ -1,4 +1,4 @@ -package de.bixilon.minosoft.gui.rendering.hud +package de.bixilon.minosoft.gui.rendering.hud.elements.text import glm_.BYTES import org.lwjgl.opengl.GL11.GL_FLOAT diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDText.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDText.kt new file mode 100644 index 000000000..2bb3d26d1 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDText.kt @@ -0,0 +1,10 @@ +package de.bixilon.minosoft.gui.rendering.hud.elements.text + +import de.bixilon.minosoft.gui.rendering.font.FontBindings + +interface HUDText { + + fun prepare(chatComponents: Map>) + fun update() {} + fun draw() {} +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDTextElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDTextElement.kt new file mode 100644 index 000000000..6a31bfd4d --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/hud/elements/text/HUDTextElement.kt @@ -0,0 +1,108 @@ +package de.bixilon.minosoft.gui.rendering.hud.elements.text + +import de.bixilon.minosoft.data.mappings.ModIdentifier +import de.bixilon.minosoft.data.text.ChatComponent +import de.bixilon.minosoft.gui.rendering.RenderWindow +import de.bixilon.minosoft.gui.rendering.font.Font +import de.bixilon.minosoft.gui.rendering.font.FontBindings +import de.bixilon.minosoft.gui.rendering.hud.HUDRenderer +import de.bixilon.minosoft.gui.rendering.hud.elements.HUDElement +import de.bixilon.minosoft.gui.rendering.shader.Shader +import de.bixilon.minosoft.gui.rendering.textures.TextureArray +import de.bixilon.minosoft.protocol.network.Connection +import glm_.glm +import glm_.mat4x4.Mat4 +import glm_.vec2.Vec2 +import org.lwjgl.opengl.GL11.GL_DEPTH_TEST +import org.lwjgl.opengl.GL11.glDisable +import org.lwjgl.opengl.GL13.GL_TEXTURE0 + +class HUDTextElement(val connection: Connection, val hudRenderer: HUDRenderer, val renderWindow: RenderWindow) : HUDElement { + private val fontBindingPerspectiveMatrices = mutableListOf(Mat4(), Mat4(), Mat4(), Mat4()) // according to FontBindings::ordinal + private lateinit var fontShader: Shader + private lateinit var hudMeshHUD: HUDFontMesh + private lateinit var fontAtlasTexture: TextureArray + private val font = Font() + private lateinit var componentsBindingMap: Map> + + var hudTextElements: MutableMap = mutableMapOf( + ModIdentifier("minosoft:debug_screen") to HUDDebugScreenElement(this), + ModIdentifier("minosoft:chat") to HUDChatElement(this), + ) + + override fun screenChangeResizeCallback(width: Int, height: Int) { + fontShader.use() + + fontBindingPerspectiveMatrices[FontBindings.LEFT_UP.ordinal] = glm.ortho(0.0f, width.toFloat(), height.toFloat(), 0.0f) + fontBindingPerspectiveMatrices[FontBindings.RIGHT_UP.ordinal] = glm.ortho(width.toFloat(), 0.0f, height.toFloat(), 0.0f) + fontBindingPerspectiveMatrices[FontBindings.RIGHT_DOWN.ordinal] = glm.ortho(width.toFloat(), 0.0f, 0.0f, height.toFloat()) + fontBindingPerspectiveMatrices[FontBindings.LEFT_DOWN.ordinal] = glm.ortho(0.0f, width.toFloat(), 0.0f, height.toFloat()) + + prepare() + } + + fun drawChatComponent(position: Vec2, binding: FontBindings, text: ChatComponent, meshData: MutableList, maxSize: Vec2) { + hudMeshHUD.unload() + text.addVerticies(position, Vec2(0, 0), fontBindingPerspectiveMatrices[binding.ordinal], binding, font, hudRenderer.hudScale, meshData, maxSize) + } + + override fun prepare() { + componentsBindingMap = mapOf( + FontBindings.LEFT_UP to mutableListOf( + "§aMinosoft (0.1-pre1)", + ), + FontBindings.RIGHT_UP to mutableListOf(), + FontBindings.RIGHT_DOWN to mutableListOf(), + FontBindings.LEFT_DOWN to mutableListOf(), + ) + for (hudTextElement in hudTextElements.values) { + hudTextElement.prepare(componentsBindingMap) + } + } + + override fun update() { + for (hudTextElement in hudTextElements.values) { + hudTextElement.update() + } + + val meshData: MutableList = mutableListOf() + + for ((binding, components) in componentsBindingMap) { + val offset = Vec2(3, 3) + + if (binding == FontBindings.RIGHT_DOWN || binding == FontBindings.LEFT_DOWN) { + components.reverse() + } + + for ((_, component) in components.withIndex()) { + val currentOffset = Vec2() + drawChatComponent(offset, binding, ChatComponent.valueOf(component), meshData, currentOffset) + offset += Vec2(0, currentOffset.y + 1) + } + } + hudMeshHUD.unload() + hudMeshHUD = HUDFontMesh(meshData.toFloatArray()) + } + + override fun init() { + font.load(connection.version.assetsManager) + fontShader = Shader("font_vertex.glsl", "font_fragment.glsl") + fontShader.load() + hudMeshHUD = HUDFontMesh(floatArrayOf()) + + + fontAtlasTexture = font.createAtlasTexture() + fontAtlasTexture.load() + } + + override fun draw() { + fontAtlasTexture.use(GL_TEXTURE0) + fontShader.use() + glDisable(GL_DEPTH_TEST) + + for (hudTextElement in hudTextElements.values) { + hudTextElement.draw() + } + hudMeshHUD.draw() + } +} 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 f357d628e..90c5cdd63 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java +++ b/src/main/java/de/bixilon/minosoft/protocol/network/Connection.java @@ -135,11 +135,18 @@ public class Connection { version.load(latch); // ToDo: show gui loader this.customMapping.setVersion(version); this.customMapping.setParentMapping(version.getMapping()); + + if (!StaticConfiguration.HEADLESS_MODE) { + this.rendering = new Rendering(this); + this.rendering.start(latch); + } + latch.waitForChange(); Log.info(String.format("Connecting to server: %s", address)); this.network.connect(address); + latch.countDown(); } catch (Exception e) { Log.printException(e, LogLevels.DEBUG); - Log.fatal(String.format("Could not load mapping for %s. This version seems to be unsupported!", version)); + Log.fatal(String.format("Could not load version %s. This version seems to be unsupported!", version)); this.lastException = new MappingsLoadingException("Mappings could not be loaded", e); setConnectionState(ConnectionStates.FAILED_NO_RETRY); } @@ -165,7 +172,6 @@ public class Connection { this.version = version; } - public void handle(ClientboundPacket p) { this.handlingQueue.add(p); } @@ -372,10 +378,6 @@ public class Connection { case FAILED_NO_RETRY -> handlePingCallbacks(null); case PLAY -> { Minosoft.CONNECTIONS.put(getConnectionId(), this); - if (!StaticConfiguration.HEADLESS_MODE) { - this.rendering = new Rendering(this); - this.rendering.start(); - } if (CLI.getCurrentConnection() == null) { CLI.setCurrentConnection(this); diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChatMessageReceiving.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChatMessageReceiving.java index 953ff4f4f..ad4b3bdbe 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChatMessageReceiving.java +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChatMessageReceiving.java @@ -56,8 +56,6 @@ public class PacketChatMessageReceiving extends ClientboundPacket { case ABOVE_HOTBAR -> "[HOTBAR] "; default -> "[CHAT] "; } + event.getMessage()); - - connection.getRenderer().getRenderWindow().getHudRenderer().getChatMessages().add(new kotlin.Pair<>(this.message, System.currentTimeMillis())); } @Override diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java b/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java index 4dba3ac0a..2c59f9b9a 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java @@ -82,6 +82,10 @@ public final class ProtocolDefinition { public static final char[] OBFUSCATED_CHARS = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".toCharArray(); + + public static final int TICKS_PER_SECOND = 20; + public static final int TICK_TIME = 1000 / TICKS_PER_SECOND; + static { // java does (why ever) not allow to directly assign a null InetAddress tempInetAddress; diff --git a/src/main/java/de/bixilon/minosoft/util/CountUpAndDownLatch.java b/src/main/java/de/bixilon/minosoft/util/CountUpAndDownLatch.java index 7d6b6cc8e..31c152ee8 100644 --- a/src/main/java/de/bixilon/minosoft/util/CountUpAndDownLatch.java +++ b/src/main/java/de/bixilon/minosoft/util/CountUpAndDownLatch.java @@ -32,6 +32,14 @@ public class CountUpAndDownLatch { } } + public void waitUntilZero(long timeout) throws InterruptedException { + synchronized (this.lock) { + while (this.count > 0) { + this.lock.wait(timeout); + } + } + } + public void countUp() { synchronized (this.lock) { this.total++; @@ -41,6 +49,9 @@ public class CountUpAndDownLatch { } public void countDown() { + if (this.count == 0) { + throw new IllegalStateException("Can not count down, counter is already 0"); + } synchronized (this.lock) { this.count--; this.lock.notifyAll();