From 134ec6207c883d35cd78980e96b02dd148245742 Mon Sep 17 00:00:00 2001 From: Bixilon Date: Wed, 28 Apr 2021 14:00:41 +0200 Subject: [PATCH] wip mipmaps --- .../bixilon/minosoft/data/text/RGBColor.java | 113 --------------- .../de/bixilon/minosoft/data/text/RGBColor.kt | 96 +++++++++++++ .../gui/rendering/textures/TextureArray.kt | 133 ++++++++++++++++-- .../textures/TextureTransparencies.kt | 9 ++ .../protocol/protocol/InByteBuffer.kt | 6 +- 5 files changed, 230 insertions(+), 127 deletions(-) delete mode 100644 src/main/java/de/bixilon/minosoft/data/text/RGBColor.java create mode 100644 src/main/java/de/bixilon/minosoft/data/text/RGBColor.kt diff --git a/src/main/java/de/bixilon/minosoft/data/text/RGBColor.java b/src/main/java/de/bixilon/minosoft/data/text/RGBColor.java deleted file mode 100644 index ae2e052a1..000000000 --- a/src/main/java/de/bixilon/minosoft/data/text/RGBColor.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Minosoft - * Copyright (C) 2020 Moritz Zwerger - * - * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. If not, see . - * - * This software is not affiliated with Mojang AB, the original developer of Minecraft. - */ - -package de.bixilon.minosoft.data.text; - -import org.checkerframework.common.value.qual.IntRange; - -public final class RGBColor implements ChatCode { - private static final float COLOR_FLOAT_DIVIDER = 255.0f; - private final int color; - - public RGBColor(int red, int green, int blue, int alpha) { - this.color = (alpha) | (blue << 8) | (green << 16) | (red << 24); - } - - public RGBColor(byte red, byte green, byte blue, byte alpha) { - this(red & 0xFF, green & 0xFF, blue & 0xFF, alpha & 0xFF); - } - - public RGBColor(int red, int green, int blue) { - this(red, green, blue, 0xFF); - } - - public RGBColor(int color) { - this.color = color; - } - - public RGBColor(String colorString) { - if (colorString.startsWith("#")) { - colorString = colorString.substring(1); - } - if (colorString.length() == 6) { - this.color = Integer.parseUnsignedInt(colorString + "ff", 16); - } else { - this.color = Integer.parseUnsignedInt(colorString, 16); - } - } - - public static RGBColor noAlpha(int color) { - return new RGBColor(color << 8 | 0xFF); - } - - @IntRange(from = 0, to = 255) - public int getAlpha() { - return (this.color & 0xFF); - } - - @IntRange(from = 0, to = 255) - public int getRed() { - return (this.color >>> 24) & 0xFF; - } - - @IntRange(from = 0, to = 1) - public float getFloatRed() { - return getRed() / COLOR_FLOAT_DIVIDER; - } - - @IntRange(from = 0, to = 255) - public int getGreen() { - return (this.color >>> 16) & 0xFF; - } - - @IntRange(from = 0, to = 1) - public float getFloatGreen() { - return getGreen() / COLOR_FLOAT_DIVIDER; - } - - @IntRange(from = 0, to = 255) - public int getBlue() { - return (this.color >>> 8) & 0xFF; - } - - @IntRange(from = 0, to = 1) - public float getFloatBlue() { - return getBlue() / COLOR_FLOAT_DIVIDER; - } - - @Override - public int hashCode() { - return this.color; - } - - @Override - public boolean equals(Object obj) { - if (super.equals(obj)) { - return true; - } - RGBColor their = (RGBColor) obj; - return getColor() == their.getColor(); - } - - @Override - public String toString() { - if (getAlpha() != 255) { - return String.format("#%08X", this.color); - } - return String.format("#%06X", (0xFFFFFF & this.color)); - } - - public int getColor() { - return this.color; - } -} diff --git a/src/main/java/de/bixilon/minosoft/data/text/RGBColor.kt b/src/main/java/de/bixilon/minosoft/data/text/RGBColor.kt new file mode 100644 index 000000000..d9e96f3da --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/text/RGBColor.kt @@ -0,0 +1,96 @@ +/* + * Minosoft + * Copyright (C) 2021 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ +package de.bixilon.minosoft.data.text + +import org.checkerframework.common.value.qual.IntRange + +class RGBColor(val color: Int) : ChatCode { + + @JvmOverloads + constructor(red: Int, green: Int, blue: Int, alpha: Int = 0xFF) : this(alpha or (blue shl 8) or (green shl 16) or (red shl 24)) + + constructor(red: Byte, green: Byte, blue: Byte, alpha: Byte = 0xFF.toByte()) : this(red.toInt() and 0xFF, green.toInt() and 0xFF, blue.toInt() and 0xFF, alpha.toInt() and 0xFF) + + constructor(colorString: String) : this(colorString.toColorInt()) + + val alpha: @IntRange(from = 0.toLong(), to = 255.toLong()) Int + get() = color and 0xFF + val red: @IntRange(from = 0.toLong(), to = 255.toLong()) Int + get() = color ushr 24 and 0xFF + val floatRed: @IntRange(from = 0.toLong(), to = 1.toLong()) Float + get() = red / COLOR_FLOAT_DIVIDER + val green: @IntRange(from = 0.toLong(), to = 255.toLong()) Int + get() = color ushr 16 and 0xFF + val floatGreen: @IntRange(from = 0.toLong(), to = 1.toLong()) Float + get() = green / COLOR_FLOAT_DIVIDER + val blue: @IntRange(from = 0.toLong(), to = 255.toLong()) Int + get() = color ushr 8 and 0xFF + val floatBlue: @IntRange(from = 0.toLong(), to = 1.toLong()) Float + get() = blue / COLOR_FLOAT_DIVIDER + + override fun hashCode(): Int { + return color + } + + override fun equals(other: Any?): Boolean { + if (super.equals(other)) { + return true + } + val their = other as RGBColor? ?: return false + return color == their.color + } + + override fun toString(): String { + return if (alpha != 255) { + String.format("#%08X", color) + } else { + String.format("#%06X", 0xFFFFFF and color) + } + } + + companion object { + private const val COLOR_FLOAT_DIVIDER = 255.0f + fun noAlpha(color: Int): RGBColor { + return RGBColor(color shl 8 or 0xFF) + } + + fun String.toColor(): RGBColor { + return RGBColor(this) + } + + fun String.toColorInt(): Int { + var colorString = this + if (colorString.startsWith("#")) { + colorString = colorString.substring(1) + } + return if (colorString.length == 6) { + Integer.parseUnsignedInt(colorString + "ff", 16) + } else { + Integer.parseUnsignedInt(colorString, 16) + } + } + + fun mix(vararg colors: RGBColor): RGBColor { + var red = 0 + var green = 0 + var blue = 0 + + for (color in colors) { + red += color.red + green += color.green + blue += color.blue + } + return RGBColor(red / colors.size, green / colors.size, blue / colors.size) + } + } +} 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 185083dfa..ada301ce4 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 @@ -16,6 +16,7 @@ package de.bixilon.minosoft.gui.rendering.textures import de.bixilon.minosoft.Minosoft import de.bixilon.minosoft.data.assets.AssetsManager import de.bixilon.minosoft.data.mappings.ResourceLocation +import de.bixilon.minosoft.data.text.RGBColor import de.bixilon.minosoft.gui.rendering.shader.Shader import de.bixilon.minosoft.util.logging.Log import de.bixilon.minosoft.util.logging.LogLevels @@ -24,10 +25,9 @@ import de.matthiasmann.twl.utils.PNGDecoder import glm_.vec2.Vec2 import glm_.vec2.Vec2i import org.lwjgl.BufferUtils -import org.lwjgl.opengl.GL12.glTexImage3D -import org.lwjgl.opengl.GL12.glTexSubImage3D import org.lwjgl.opengl.GL30.* import org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER +import org.lwjgl.opengl.GL31.glBindBuffer import java.nio.ByteBuffer class TextureArray(val allTextures: MutableList) { @@ -119,17 +119,129 @@ class TextureArray(val allTextures: MutableList) { glBindTexture(GL_TEXTURE_2D_ARRAY, textureId) glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT) glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT) - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST) - // glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR) // ToDo: This breaks transparency again + // glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST) glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, MAX_MIPMAP_LEVELS - 1) - glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, resolution, resolution, textures.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, null as ByteBuffer?) - - for (texture in textures) { - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, texture.arrayLayer, texture.size.x, texture.size.y, 1, GL_RGBA, GL_UNSIGNED_BYTE, texture.buffer!!) - texture.buffer = null + for (i in 0 until MAX_MIPMAP_LEVELS) { + glTexImage3D(GL_TEXTURE_2D_ARRAY, i, GL_RGBA, resolution shr i, resolution shr i, textures.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, null as ByteBuffer?) } - // glGenerateMipmap(GL_TEXTURE_2D_ARRAY) + for (texture in textures) { + var lastBuffer = texture.buffer!! + var lastSize = texture.size + for (i in 0 until MAX_MIPMAP_LEVELS) { + val size = Vec2i(texture.size.x shr i, texture.size.y shr i) + if (i != 0) { + lastBuffer = generateMipmap(lastBuffer, lastSize, size) + lastSize = size + } + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, i, 0, 0, texture.arrayLayer, size.x, size.y, i + 1, GL_RGBA, GL_UNSIGNED_BYTE, lastBuffer) + } + } + } + + private fun ByteBuffer.getRGB(start: Int): RGBColor { + return RGBColor(get(start), get(start + 1), get(start + 2), get(start + 3)) + } + + private fun ByteBuffer.setRGB(start: Int, color: RGBColor) { + put(start, color.red.toByte()) + put(start + 1, color.green.toByte()) + put(start + 2, color.blue.toByte()) + put(start + 3, color.alpha.toByte()) + } + + @Deprecated(message = "This is garbage, will be improved soon...") + private fun generateMipmap(biggerBuffer: ByteBuffer, oldSize: Vec2i, newSize: Vec2i): ByteBuffer { + val sizeFactor = oldSize / newSize + val buffer = BufferUtils.createByteBuffer(biggerBuffer.capacity() shr 1) + buffer.limit(buffer.capacity()) + + fun getRGB(x: Int, y: Int): RGBColor { + return biggerBuffer.getRGB((y * oldSize.x + x) * 4) + } + + fun setRGB(x: Int, y: Int, color: RGBColor) { + buffer.setRGB((y * newSize.x + x) * 4, color) + } + + for (y in 0 until newSize.y) { + for (x in 0 until newSize.x) { + + // check what is the most used transparency + val transparencyPixelCount = IntArray(TextureTransparencies.VALUES.size) + for (mixY in 0 until sizeFactor.y) { + for (mixX in 0 until sizeFactor.x) { + val color = getRGB(x * sizeFactor.x + mixX, y * sizeFactor.y + mixY) + when (color.alpha) { + 255 -> transparencyPixelCount[TextureTransparencies.OPAQUE.ordinal]++ + 0 -> transparencyPixelCount[TextureTransparencies.TRANSPARENT.ordinal]++ + else -> transparencyPixelCount[TextureTransparencies.TRANSLUCENT.ordinal]++ + } + } + } + var largest = 0 + for (count in transparencyPixelCount) { + if (count > largest) { + largest = count + } + } + var transparency: TextureTransparencies = TextureTransparencies.OPAQUE + for ((index, count) in transparencyPixelCount.withIndex()) { + if (count >= largest) { + transparency = TextureTransparencies.VALUES[index] + break + } + } + + var count = 0 + var red = 0 + var green = 0 + var blue = 0 + var alpha = 0 + + // make magic for the most used transparency + for (mixY in 0 until sizeFactor.y) { + for (mixX in 0 until sizeFactor.x) { + val color = getRGB(x * sizeFactor.x + mixX, y * sizeFactor.y + mixY) + when (transparency) { + TextureTransparencies.OPAQUE -> { + if (color.alpha != 0xFF) { + continue + } + red += color.red + green += color.green + blue += color.blue + alpha += color.alpha + count++ + } + TextureTransparencies.TRANSPARENT -> { + } + TextureTransparencies.TRANSLUCENT -> { + red += color.red + green += color.green + blue += color.blue + alpha += color.alpha + count++ + } + } + } + } + + + + + + if (count == 0) { + count++ + } + setRGB(x, y, RGBColor(red / count, green / count, blue / count, alpha / count)) + } + } + + buffer.rewind() + return buffer } @@ -146,6 +258,7 @@ class TextureArray(val allTextures: MutableList) { companion object { val TEXTURE_RESOLUTION_ID_MAP = arrayOf(16, 32, 64, 128, 256, 512, 1024) // A 12x12 texture will be saved in texture id 0 (in 0 are only 16x16 textures). Animated textures get split const val TEXTURE_MAX_RESOLUTION = 1024 + const val MAX_MIPMAP_LEVELS = 5 private const val INTS_PER_ANIMATED_TEXTURE = 4 } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureTransparencies.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureTransparencies.kt index 480e8021e..4a90d6b68 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureTransparencies.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/textures/TextureTransparencies.kt @@ -13,8 +13,17 @@ package de.bixilon.minosoft.gui.rendering.textures +import de.bixilon.minosoft.util.KUtil +import de.bixilon.minosoft.util.enum.ValuesEnum + enum class TextureTransparencies { OPAQUE, TRANSPARENT, TRANSLUCENT, + ; + + companion object : ValuesEnum { + override val VALUES: Array = values() + override val NAME_MAP: Map = KUtil.getEnumValues(VALUES) + } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/InByteBuffer.kt b/src/main/java/de/bixilon/minosoft/protocol/protocol/InByteBuffer.kt index 73cf540f0..691ae3e00 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/InByteBuffer.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/InByteBuffer.kt @@ -25,7 +25,6 @@ import de.bixilon.minosoft.data.text.ChatComponent import de.bixilon.minosoft.protocol.network.connection.Connection import de.bixilon.minosoft.util.Util import de.bixilon.minosoft.util.nbt.tag.NBTTagTypes -import de.bixilon.minosoft.util.nbt.tag.NBTTagTypes.Companion.VALUES import glm_.vec2.Vec2i import glm_.vec3.Vec3 import glm_.vec3.Vec3i @@ -370,7 +369,7 @@ open class InByteBuffer { NBTTagTypes.COMPOUND -> { val out: MutableMap = mutableMapOf() while (true) { - val compoundTagType = VALUES[readUnsignedByte()] + val compoundTagType = NBTTagTypes.VALUES[readUnsignedByte()] if (compoundTagType === NBTTagTypes.END) { // end tag break @@ -396,7 +395,7 @@ open class InByteBuffer { InByteBuffer(Util.decompressGzip(readByteArray(length)), connection!!).readNBTTag(false) } } - val type = VALUES[readUnsignedByte()] + val type = NBTTagTypes.VALUES[readUnsignedByte()] if (type === NBTTagTypes.COMPOUND) { var name = readString(readUnsignedShort()) // ToDo } @@ -406,5 +405,4 @@ open class InByteBuffer { fun getBase64(): String { return String(Base64.getEncoder().encode(readRest())) } - }