wip mipmaps

This commit is contained in:
Bixilon 2021-04-28 14:00:41 +02:00
parent 8f2edfcc75
commit 134ec6207c
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
5 changed files with 230 additions and 127 deletions

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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;
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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)
}
}
}

View File

@ -16,6 +16,7 @@ package de.bixilon.minosoft.gui.rendering.textures
import de.bixilon.minosoft.Minosoft import de.bixilon.minosoft.Minosoft
import de.bixilon.minosoft.data.assets.AssetsManager import de.bixilon.minosoft.data.assets.AssetsManager
import de.bixilon.minosoft.data.mappings.ResourceLocation 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.gui.rendering.shader.Shader
import de.bixilon.minosoft.util.logging.Log import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels import de.bixilon.minosoft.util.logging.LogLevels
@ -24,10 +25,9 @@ import de.matthiasmann.twl.utils.PNGDecoder
import glm_.vec2.Vec2 import glm_.vec2.Vec2
import glm_.vec2.Vec2i import glm_.vec2.Vec2i
import org.lwjgl.BufferUtils 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.GL30.*
import org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER import org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER
import org.lwjgl.opengl.GL31.glBindBuffer
import java.nio.ByteBuffer import java.nio.ByteBuffer
class TextureArray(val allTextures: MutableList<Texture>) { class TextureArray(val allTextures: MutableList<Texture>) {
@ -119,17 +119,129 @@ class TextureArray(val allTextures: MutableList<Texture>) {
glBindTexture(GL_TEXTURE_2D_ARRAY, textureId) 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_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, 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)
// 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_MIPMAP_NEAREST)
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_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 (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?)
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
} }
// 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<Texture>) {
companion object { 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 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 TEXTURE_MAX_RESOLUTION = 1024
const val MAX_MIPMAP_LEVELS = 5
private const val INTS_PER_ANIMATED_TEXTURE = 4 private const val INTS_PER_ANIMATED_TEXTURE = 4
} }

View File

@ -13,8 +13,17 @@
package de.bixilon.minosoft.gui.rendering.textures package de.bixilon.minosoft.gui.rendering.textures
import de.bixilon.minosoft.util.KUtil
import de.bixilon.minosoft.util.enum.ValuesEnum
enum class TextureTransparencies { enum class TextureTransparencies {
OPAQUE, OPAQUE,
TRANSPARENT, TRANSPARENT,
TRANSLUCENT, TRANSLUCENT,
;
companion object : ValuesEnum<TextureTransparencies> {
override val VALUES: Array<TextureTransparencies> = values()
override val NAME_MAP: Map<String, TextureTransparencies> = KUtil.getEnumValues(VALUES)
}
} }

View File

@ -25,7 +25,6 @@ import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.protocol.network.connection.Connection import de.bixilon.minosoft.protocol.network.connection.Connection
import de.bixilon.minosoft.util.Util import de.bixilon.minosoft.util.Util
import de.bixilon.minosoft.util.nbt.tag.NBTTagTypes import de.bixilon.minosoft.util.nbt.tag.NBTTagTypes
import de.bixilon.minosoft.util.nbt.tag.NBTTagTypes.Companion.VALUES
import glm_.vec2.Vec2i import glm_.vec2.Vec2i
import glm_.vec3.Vec3 import glm_.vec3.Vec3
import glm_.vec3.Vec3i import glm_.vec3.Vec3i
@ -370,7 +369,7 @@ open class InByteBuffer {
NBTTagTypes.COMPOUND -> { NBTTagTypes.COMPOUND -> {
val out: MutableMap<String, Any> = mutableMapOf() val out: MutableMap<String, Any> = mutableMapOf()
while (true) { while (true) {
val compoundTagType = VALUES[readUnsignedByte()] val compoundTagType = NBTTagTypes.VALUES[readUnsignedByte()]
if (compoundTagType === NBTTagTypes.END) { if (compoundTagType === NBTTagTypes.END) {
// end tag // end tag
break break
@ -396,7 +395,7 @@ open class InByteBuffer {
InByteBuffer(Util.decompressGzip(readByteArray(length)), connection!!).readNBTTag(false) InByteBuffer(Util.decompressGzip(readByteArray(length)), connection!!).readNBTTag(false)
} }
} }
val type = VALUES[readUnsignedByte()] val type = NBTTagTypes.VALUES[readUnsignedByte()]
if (type === NBTTagTypes.COMPOUND) { if (type === NBTTagTypes.COMPOUND) {
var name = readString(readUnsignedShort()) // ToDo var name = readString(readUnsignedShort()) // ToDo
} }
@ -406,5 +405,4 @@ open class InByteBuffer {
fun getBase64(): String { fun getBase64(): String {
return String(Base64.getEncoder().encode(readRest())) return String(Base64.getEncoder().encode(readRest()))
} }
} }