From d7a10ae0822bc75a4a10d5990e363438e4c3d1df Mon Sep 17 00:00:00 2001 From: Bixilon Date: Tue, 9 Feb 2021 22:08:48 +0100 Subject: [PATCH] rendering: improve texture loading, load font from minecraft assets, render single char --- .../minosoft/gui/rendering/ChunkPreparer.kt | 9 +- .../minosoft/gui/rendering/RenderWindow.kt | 47 +++++++--- .../minosoft/gui/rendering/font/Font.kt | 43 +++++++-- .../minosoft/gui/rendering/font/FontChar.kt | 23 ++++- .../minosoft/gui/rendering/font/FontLoader.kt | 89 +++++++++++++++++++ .../gui/rendering/font/FontProvider.kt | 8 ++ .../gui/rendering/textures/Texture.kt | 31 +++++++ .../gui/rendering/textures/TextureArray.kt | 34 +++++-- .../gui/rendering/textures/TextureLoader.kt | 30 ------- .../rendering/shader/font_fragment.glsl | 15 ++-- .../assets/rendering/shader/font_vertex.glsl | 3 +- 11 files changed, 259 insertions(+), 73 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/gui/rendering/font/FontLoader.kt create mode 100644 src/main/java/de/bixilon/minosoft/gui/rendering/font/FontProvider.kt delete mode 100644 src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureLoader.kt 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 2c34c693f..2afc25f3d 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/ChunkPreparer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/ChunkPreparer.kt @@ -56,10 +56,11 @@ object ChunkPreparer { section.getBlock(position.getLocationByDirection(Directions.EAST)) } - fun drawBlock() { - block.blockModel.render(position, data, arrayOf(blockBelow, blockAbove, blockNorth, blockSouth, blockWest, blockEast)) - } - drawBlock() + // if(block.fullIdentifier != "minecraft:dropper"){ + // continue + // } + + block.blockModel.render(position, data, arrayOf(blockBelow, blockAbove, blockNorth, blockSouth, blockWest, blockEast)) } return data.toFloatArray() } 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 b3e2080cd..64eefa32e 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt @@ -12,6 +12,7 @@ import de.bixilon.minosoft.gui.rendering.textures.TextureArray import de.bixilon.minosoft.protocol.network.Connection import de.bixilon.minosoft.protocol.packets.serverbound.play.PacketPlayerPositionAndRotationSending import de.bixilon.minosoft.util.CountUpAndDownLatch +import glm_.vec2.Vec2 import org.lwjgl.* import org.lwjgl.glfw.Callbacks import org.lwjgl.glfw.GLFW.* @@ -110,17 +111,14 @@ class RenderWindow(private val connection: Connection) { // Make the window visible - glfwShowWindow(windowId) GL.createCapabilities() glClearColor(137 / 256f, 207 / 256f, 240 / 256f, 1.0f) glEnable(GL_DEPTH_TEST) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - minecraftTextures = TextureArray(connection.version.assetsManager, resolveBlockTextureIds(connection.version.mapping.blockMap.values)) + minecraftTextures = TextureArray.createTextureArray(connection.version.assetsManager, resolveBlockTextureIds(connection.version.mapping.blockMap.values), 16, 16) // ToDo :Remove fixed size minecraftTextures.load() - fontAtlasTexture = TextureArray(connection.version.assetsManager, listOf(Texture("font/unicode_page_00", 0)), 256) - fontAtlasTexture.load() chunkShader = Shader("chunk_vertex.glsl", "chunk_fragment.glsl") chunkShader.load() @@ -132,18 +130,37 @@ class RenderWindow(private val connection: Connection) { fontShader = Shader("font_vertex.glsl", "font_fragment.glsl") fontShader.load() fontShader.use() - fontShader.setFloat("atlasSize", 256f) val font = Font() - val char = font.chars['§']!! - font2DToDraw.add(Font2DMesh(floatArrayOf( - -0.5f, -0.5f, char.column * 16f, char.row * 16f + char.width, font.atlasOffset + char.atlasPage.toFloat(), - -0.5f, 0f, char.column * 16f, char.row * 16f, font.atlasOffset + char.atlasPage.toFloat(), - 0f, -0.5f, char.column * 16f + char.width, char.row * 16f + char.width, font.atlasOffset + char.atlasPage.toFloat(), - 0f, -0.5f, char.column * 16f + char.width, char.row * 16f + char.width, font.atlasOffset + char.atlasPage.toFloat(), - -0.5f, 0f, char.column * 16f, char.row * 16f, font.atlasOffset + char.atlasPage.toFloat(), - 0f, 0f, char.column * 16f + char.width, char.row * 16f, font.atlasOffset + char.atlasPage.toFloat(), - ))) + font.load(connection.version.assetsManager) + + fun drawLetterVertex(position: Vec2, uv: Vec2, atlasPage: Int, meshData: MutableList) { + meshData.add(position.x) + meshData.add(position.y) + meshData.add(uv.x) + meshData.add(uv.y) + meshData.add(atlasPage.toFloat()) + } + + fontAtlasTexture = font.createAtlasTexture() + fontAtlasTexture.load() + + + fun drawLetter(position: Vec2, char: Char) { + val fontChar = font.getChar(char) + val meshData: MutableList = mutableListOf() + + drawLetterVertex(Vec2(-0.5f, -0.5f), fontChar.uvLeftDown, fontChar.atlasTextureIndex, meshData) + drawLetterVertex(Vec2(-0.5f, 0f), fontChar.uvLeftUp, fontChar.atlasTextureIndex, meshData) + drawLetterVertex(Vec2(0f, -0.5f), fontChar.uvRightDown, fontChar.atlasTextureIndex, meshData) + drawLetterVertex(Vec2(0f, -0.5f), fontChar.uvRightDown, fontChar.atlasTextureIndex, meshData) + drawLetterVertex(Vec2(-0.5f, 0f), fontChar.uvLeftUp, fontChar.atlasTextureIndex, meshData) + drawLetterVertex(Vec2(0f, 0f), fontChar.uvRightUp, fontChar.atlasTextureIndex, meshData) + + font2DToDraw.add(Font2DMesh(meshData.toFloatArray())) + } + + drawLetter(Vec2(0, 0), 'ä') glfwSetWindowSizeCallback(windowId, object : GLFWWindowSizeCallback() { @@ -155,6 +172,8 @@ class RenderWindow(private val connection: Connection) { } }) + glfwShowWindow(windowId) + latch?.countDown() } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/Font.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/Font.kt index 2f0f66912..ae0bbd3aa 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/font/Font.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/Font.kt @@ -1,17 +1,44 @@ package de.bixilon.minosoft.gui.rendering.font +import de.bixilon.minosoft.data.assets.AssetsManager +import de.bixilon.minosoft.gui.rendering.textures.Texture +import de.bixilon.minosoft.gui.rendering.textures.TextureArray class Font { - val chars: MutableMap = mutableMapOf() - val atlasOffset = 0 + lateinit var providers: List - init { - for (page in 0 until 256) { - for (x in 0 until 16) { - for (y in 0 until 16) { - chars[(page * 256 + (x * 16 + y)).toChar()] = FontChar(page, x, y) - } + fun load(assetsManager: AssetsManager) { + providers = FontLoader.loadFontProviders(assetsManager) + } + + fun getChar(char: Char): FontChar { + for (provider in providers) { + provider.chars[char]?.let { + return it } } + throw IllegalStateException("$char can not be rendered!") + } + + fun createAtlasTexture(): TextureArray { + val textures: MutableList = mutableListOf() + for (provider in providers) { + for (atlasPage in provider.atlasTextures) { + textures.add(atlasPage) + } + } + + val textureArray = TextureArray.createTextureArray(textures = textures) + + + val atlasWidthSinglePixel = 1f / textureArray.maxWidth + val atlasHeightSinglePixel = 1f / textureArray.maxHeight + + for (provider in providers) { + for (char in provider.chars.values) { + char.calculateUV(provider.width, atlasWidthSinglePixel, atlasHeightSinglePixel) + } + } + return textureArray } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/FontChar.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/FontChar.kt index 0cfa59685..575f4f049 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/font/FontChar.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/FontChar.kt @@ -1,6 +1,25 @@ package de.bixilon.minosoft.gui.rendering.font -class FontChar(val atlasPage: Int, val row: Int, val column: Int) { - val width = 16 +import glm_.vec2.Vec2 +data class FontChar( + val atlasPage: Int, + val atlasTextureIndex: Int, + val row: Int, + val column: Int, + val startPixel: Int, + val endPixel: Int, + val height: Int, +) { + lateinit var uvLeftUp: Vec2 + lateinit var uvRightUp: Vec2 + lateinit var uvRightDown: Vec2 + lateinit var uvLeftDown: Vec2 + + fun calculateUV(letterWidth: Int, atlasWidthSinglePixel: Float, atlasHeightSinglePixel: Float) { + uvLeftUp = Vec2(atlasWidthSinglePixel * (letterWidth * column + startPixel), atlasHeightSinglePixel * (height * row)) + uvRightUp = Vec2(atlasWidthSinglePixel * (letterWidth * column + endPixel), atlasHeightSinglePixel * (height * row)) + uvRightDown = Vec2(atlasWidthSinglePixel * (letterWidth * column + endPixel), atlasHeightSinglePixel * (height * (row + 1))) + uvLeftDown = Vec2(atlasWidthSinglePixel * (letterWidth * column + startPixel), atlasHeightSinglePixel * (height * (row + 1))) + } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/FontLoader.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/FontLoader.kt new file mode 100644 index 000000000..e07cbb93a --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/FontLoader.kt @@ -0,0 +1,89 @@ +package de.bixilon.minosoft.gui.rendering.font + +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import de.bixilon.minosoft.data.assets.AssetsManager +import de.bixilon.minosoft.data.mappings.ModIdentifier +import de.bixilon.minosoft.gui.rendering.textures.Texture +import de.bixilon.minosoft.gui.rendering.textures.TextureArray +import java.io.InputStream + +object FontLoader { + private const val FONT_ATLAS_SIZE = 16 + private val MISSING_UNICODE_PAGES = listOf(0x08, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xEE, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8) + + private fun getCharArray(data: JsonArray): List { + val ret: MutableList = mutableListOf() + for (string in data) { + ret.addAll(string.asString.toCharArray().toList()) + } + return ret + } + + private fun loadBitmapFontProvider(atlasPath: ModIdentifier, height: Int? = 8, ascent: Int, chars: List, assetsManager: AssetsManager, atlasOffset: Int): FontProvider { + val width = if (ascent == 7) { + 8 + } else { + 9 + } + val provider = FontProvider(width) + val atlasTexture = Texture((atlasPath.mod + "/textures/" + atlasPath.identifier), atlasOffset) + atlasTexture.load(assetsManager) + provider.atlasTextures.add(atlasTexture) + val height = height ?: atlasTexture.width / 16 + for ((i, char) in chars.withIndex()) { + val fontChar = FontChar(0, atlasOffset, i / FONT_ATLAS_SIZE, i % FONT_ATLAS_SIZE, 0, width, height) + provider.chars[char] = fontChar + } + return provider + } + + + private fun loadUnicodeFontProvider(template: ModIdentifier, sizes: InputStream, assetsManager: AssetsManager, atlasOffset: Int): FontProvider { + val provider = FontProvider(16) + var i = 0 + lateinit var currentAtlasTexture: Texture + while (sizes.available() > 0) { + if (i % 256 == 0) { + currentAtlasTexture = if (MISSING_UNICODE_PAGES.contains(i / 256)) { + // ToDo: Why is this texture missing in minecraft? + Texture(TextureArray.DEBUG_TEXTURE.name, i / 256) + } else { + // new page (texture) + Texture((template.mod + "/textures/" + template.identifier).format("%02x".format(i / 256)), atlasOffset + (i / 256)) + } + currentAtlasTexture.load(assetsManager) + provider.atlasTextures.add(currentAtlasTexture) + } + val sizeByte = sizes.read() + val fontChar = FontChar((i / 256), atlasOffset + (i / 256), (i % 256) / FONT_ATLAS_SIZE, (i % 256) % FONT_ATLAS_SIZE, (sizeByte shr 4) and 0x0F, sizeByte and 0x0F, 16) + provider.chars[i.toChar()] = fontChar + i++ + } + return provider + } + + fun loadFontProvider(data: JsonObject, assetsManager: AssetsManager, atlasTextureOffset: Int): FontProvider { + return when (data["type"].asString) { + "bitmap" -> { + loadBitmapFontProvider(ModIdentifier(data["file"].asString), data["height"]?.asInt, data["ascent"].asInt, getCharArray(data["chars"].asJsonArray), assetsManager, atlasTextureOffset) + } + "legacy_unicode" -> { + loadUnicodeFontProvider(ModIdentifier(data["template"].asString), assetsManager.readAssetAsStream(ModIdentifier(data["sizes"].asString)), assetsManager, atlasTextureOffset) + } + else -> throw IllegalArgumentException("${data["type"]} is not a valid font provider type!") + } + } + + + fun loadFontProviders(assetsManager: AssetsManager): List { + val ret: MutableList = mutableListOf() + var atlasTextureOffset = 0 + for (providerElement in assetsManager.readJsonAsset("minecraft/font/default.json").asJsonObject["providers"].asJsonArray) { + val provider = loadFontProvider(providerElement.asJsonObject, assetsManager, atlasTextureOffset) + atlasTextureOffset += provider.atlasTextures.size + ret.add(provider) + } + return ret + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/FontProvider.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/FontProvider.kt new file mode 100644 index 000000000..e21a2c9c1 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/FontProvider.kt @@ -0,0 +1,8 @@ +package de.bixilon.minosoft.gui.rendering.font + +import de.bixilon.minosoft.gui.rendering.textures.Texture + +class FontProvider(val width: Int) { + val chars: MutableMap = mutableMapOf() + val atlasTextures: MutableList = mutableListOf() +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/textures/Texture.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/textures/Texture.kt index d1015e29d..6eb2cb2bb 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/textures/Texture.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/textures/Texture.kt @@ -1,9 +1,40 @@ package de.bixilon.minosoft.gui.rendering.textures +import de.bixilon.minosoft.data.assets.AssetsManager +import de.matthiasmann.twl.utils.PNGDecoder +import org.lwjgl.BufferUtils +import java.nio.ByteBuffer + class Texture( val name: String, val id: Int, ) { + var width: Int = 0 + var height: Int = 0 var isTransparent: Boolean = false + lateinit var buffer: ByteBuffer + var loaded = false + + fun load(assetsManager: AssetsManager) { + if (loaded) { + return + } + val texturePath = if (name.endsWith(".png")) { + name + } else { + "minecraft/textures/${name}.png" + } + val decoder = PNGDecoder(assetsManager.readAssetAsStream(texturePath)) + buffer = BufferUtils.createByteBuffer(decoder.width * decoder.height * PNGDecoder.Format.RGBA.numComponents) + decoder.decode(buffer, decoder.width * PNGDecoder.Format.RGBA.numComponents, PNGDecoder.Format.RGBA) + width = decoder.width + height = decoder.height + buffer.flip() + if (TextureArray.TRANSPARENT_TEXTURES.contains(name)) { // ToDo: this should not be hardcoded! + isTransparent = true + } + buffer.flip() + loaded = true + } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureArray.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureArray.kt index 1fa24d405..c96312e62 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureArray.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureArray.kt @@ -9,7 +9,7 @@ import org.lwjgl.opengl.GL30.GL_TEXTURE_2D_ARRAY import org.lwjgl.opengl.GL30.glGenerateMipmap import java.nio.ByteBuffer -class TextureArray(private val assetsManager: AssetsManager, private val textures: List, private val size: Int = 16) { +class TextureArray(private val textures: List, val maxWidth: Int, val maxHeight: Int) { var textureId = 0 fun load(): Int { @@ -21,12 +21,10 @@ class TextureArray(private val assetsManager: AssetsManager, private val texture glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST) - // load and generate the texture - val textureMap: Map = TextureLoader.loadTextureArray(assetsManager, textures) - glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, size, size, textures.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, null as ByteBuffer?) + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, maxWidth, maxHeight, textures.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, null as ByteBuffer?) - for ((key, value) in textureMap) { - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, key.id, size, size, 1, GL_RGBA, GL_UNSIGNED_BYTE, value) + for (texture in textures) { + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, texture.id, texture.width, texture.height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texture.buffer) } glGenerateMipmap(GL_TEXTURE_2D_ARRAY) return textureId @@ -40,5 +38,29 @@ class TextureArray(private val assetsManager: AssetsManager, private val texture companion object { val DEBUG_TEXTURE = Texture("block/debug", 0) + val TRANSPARENT_TEXTURES = listOf("block/glass") + + fun createTextureArray(assetsManager: AssetsManager? = null, textures: List, maxWidth: Int = -1, maxHeight: Int = -1): TextureArray { + var calculatedMaxWidth = 0 + var calculatedMaxHeight = 0 + for (texture in textures) { + if (!texture.loaded) { + texture.load(assetsManager!!) + } + if (texture.width > calculatedMaxWidth) { + calculatedMaxWidth = texture.width + } + if (texture.height > calculatedMaxHeight) { + calculatedMaxHeight = texture.height + } + } + if (maxWidth != -1) { + calculatedMaxWidth = maxWidth + } + if (maxHeight != -1) { + calculatedMaxHeight = maxWidth + } + return TextureArray(textures, calculatedMaxWidth, calculatedMaxHeight) + } } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureLoader.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureLoader.kt deleted file mode 100644 index 42e84771f..000000000 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureLoader.kt +++ /dev/null @@ -1,30 +0,0 @@ -package de.bixilon.minosoft.gui.rendering.textures - -import de.bixilon.minosoft.data.assets.AssetsManager -import de.matthiasmann.twl.utils.PNGDecoder -import org.lwjgl.BufferUtils -import java.nio.ByteBuffer - - -object TextureLoader { - private val TRANSPARENT_TEXTURES = listOf("block/glass") - fun loadTextureArray(assetsManager: AssetsManager, textures: List): Map { - val result: MutableMap = mutableMapOf() - for (texture in textures) { - result[texture] = loadTexture(assetsManager, texture) - } - return result.toMap() - } - - private fun loadTexture(assetsManager: AssetsManager, texture: Texture): ByteBuffer { - val decoder = PNGDecoder(assetsManager.readAssetAsStream("minecraft/textures/${texture.name}.png")) - val buffer = BufferUtils.createByteBuffer(decoder.width * decoder.height * PNGDecoder.Format.RGBA.numComponents) - decoder.decode(buffer, decoder.width * PNGDecoder.Format.RGBA.numComponents, PNGDecoder.Format.RGBA) - buffer.flip() - if (TRANSPARENT_TEXTURES.contains(texture.name)) { // ToDo: this should not be hardcoded! - texture.isTransparent = true - } - buffer.flip() - return buffer - } -} diff --git a/src/main/resources/assets/rendering/shader/font_fragment.glsl b/src/main/resources/assets/rendering/shader/font_fragment.glsl index 9ffa5e269..58e7320c9 100644 --- a/src/main/resources/assets/rendering/shader/font_fragment.glsl +++ b/src/main/resources/assets/rendering/shader/font_fragment.glsl @@ -8,12 +8,13 @@ uniform sampler2DArray texureArray; void main() { - vec4 texColor = texture(texureArray, passTextureCoordinates); - if (texColor.a == 0) { - discard; + vec4 textureColor = texture(texureArray, passTextureCoordinates); + if (textureColor.a == 0) { + textureColor.a = 0.4f; + //discard; } - texColor.r = 0.8f; - texColor.g = 0.8f; - texColor.b = 0.0f; - outColor = texColor; + textureColor.r = 0.8f; + textureColor.g = 0.8f; + textureColor.b = 0.0f; + outColor = textureColor; } diff --git a/src/main/resources/assets/rendering/shader/font_vertex.glsl b/src/main/resources/assets/rendering/shader/font_vertex.glsl index dc1c2bd8a..9090a09ce 100644 --- a/src/main/resources/assets/rendering/shader/font_vertex.glsl +++ b/src/main/resources/assets/rendering/shader/font_vertex.glsl @@ -5,9 +5,8 @@ layout (location = 2) in float textureLayer; out vec3 passTextureCoordinates; -uniform float atlasSize; void main() { gl_Position = vec4(inPosition, 0.0f, 1.0f); - passTextureCoordinates = vec3(textureIndex / atlasSize, textureLayer); + passTextureCoordinates = vec3(textureIndex, textureLayer); }