load dynamic textures async, fix some tab list bugs

This commit is contained in:
Bixilon 2022-05-02 22:24:04 +02:00
parent e2df230da4
commit e7f3891cc4
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
19 changed files with 145 additions and 36 deletions

View File

@ -27,6 +27,7 @@ import de.bixilon.minosoft.data.accounts.types.offline.OfflineAccount
import de.bixilon.minosoft.data.player.properties.PlayerProperties
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import java.util.*
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes(
@ -42,6 +43,7 @@ abstract class Account(
abstract val properties: PlayerProperties?
@get:JsonIgnore @set:JsonIgnore open var state: AccountStates by watched(AccountStates.UNCHECKED)
@get:JsonIgnore open var error: Throwable? by watched(null)
abstract val uuid: UUID
@Transient
@JsonIgnore

View File

@ -32,7 +32,7 @@ import java.net.ConnectException
import java.util.*
class MicrosoftAccount(
val uuid: UUID,
override val uuid: UUID,
username: String,
@field:JsonProperty private var msa: MicrosoftTokens,
@field:JsonProperty private var minecraft: MinecraftTokens,

View File

@ -38,7 +38,7 @@ import java.util.*
class MojangAccount(
override val id: String,
username: String,
val uuid: UUID,
override val uuid: UUID,
val email: String,
@field:JsonProperty private var accessToken: String,
override val properties: PlayerProperties?,

View File

@ -21,9 +21,11 @@ import de.bixilon.minosoft.data.player.properties.PlayerProperties
import de.bixilon.minosoft.data.registries.CompanionResourceLocation
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import java.util.*
class OfflineAccount(username: String) : Account(username) {
override val id: String = username
override val uuid: UUID = UUID("OfflinePlayer:$username".hashCode().toLong(), 0L) // ToDo
override val type: ResourceLocation = RESOURCE_LOCATION
override var state: AccountStates
get() = AccountStates.WORKING

View File

@ -22,7 +22,7 @@ import de.bixilon.minosoft.data.player.properties.textures.PlayerTextures
import java.net.URL
import java.util.*
class PlayerProperties(
data class PlayerProperties(
@JsonInclude(JsonInclude.Include.NON_EMPTY)
val textures: PlayerTextures? = null,
) {

View File

@ -17,6 +17,9 @@ import com.fasterxml.jackson.annotation.JsonIgnore
import de.bixilon.kutil.url.URLUtil.checkWeb
import de.bixilon.minosoft.assets.util.FileAssetsUtil
import de.bixilon.minosoft.assets.util.FileUtil
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
import java.net.URL
import java.util.*
@ -33,7 +36,9 @@ open class PlayerTexture(
check(urlMatches(url, ALLOWED_DOMAINS) && !urlMatches(url, BLOCKED_DOMAINS)) { "URL hostname is not allowed!" }
}
@Synchronized
fun read(): ByteArray {
this.data?.let { return it }
val sha256 = when (url.host) {
"textures.minecraft.net" -> url.file.split("/").last()
else -> TODO("Can not get texture identifier!")
@ -50,6 +55,7 @@ open class PlayerTexture(
throw IllegalStateException("Texture is too big!")
}
val data = FileAssetsUtil.saveAndGet(input)
Log.log(LogMessageType.ASSETS, LogLevels.VERBOSE) { "Downloaded skin ($url)" }
return data.second
}

View File

@ -22,7 +22,7 @@ import de.bixilon.minosoft.util.YggdrasilUtil
import de.bixilon.minosoft.util.json.Jackson
import java.util.*
class PlayerTextures(
data class PlayerTextures(
@JsonInclude(JsonInclude.Include.NON_EMPTY) val name: String?,
@JsonInclude(JsonInclude.Include.NON_EMPTY) val uuid: UUID?,
@JsonInclude(JsonInclude.Include.NON_EMPTY) val date: Date?,

View File

@ -211,6 +211,9 @@ class RenderWindow(
connection.fireEvent(ResizeWindowEvent(previousSize = Vec2i(0, 0), size = window.size))
textureManager.dynamicTextures.activate()
textureManager.staticTextures.activate()
Log.log(LogMessageType.RENDERING_LOADING) { "Rendering is fully prepared in ${stopwatch.totalTime()}" }
initialized = true
latch.dec()

View File

@ -22,7 +22,9 @@ import de.bixilon.minosoft.gui.rendering.gui.elements.Element
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIMesh
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions
import de.bixilon.minosoft.gui.rendering.system.base.texture.ShaderIdentifiable
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTexture
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTextureState
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2Util.EMPTY
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY
@ -33,12 +35,15 @@ open class DynamicImageElement(
uvEnd: Vec2 = Vec2(1.0f, 1.0f),
size: Vec2i = Vec2i.EMPTY,
tint: RGBColor = ChatColors.WHITE,
parent: Element? = null,
) : Element(guiRenderer, GUIMesh.GUIMeshStruct.FLOATS_PER_VERTEX * 6) {
var texture: DynamicTexture? = texture
var texture: DynamicTexture? = null
set(value) {
field?.usages?.decrementAndGet()
field?.onStateChange = null
value?.usages?.incrementAndGet()
value?.onStateChange = { forceApply() }
field = value
cacheUpToDate = false
}
@ -74,11 +79,20 @@ open class DynamicImageElement(
init {
this.size = size
texture?.usages?.incrementAndGet()
this.texture = texture
this.parent = parent
}
private fun getAvailableTexture(): ShaderIdentifiable {
val texture = texture ?: return renderWindow.textureManager.whiteTexture.texture
if (texture.state != DynamicTextureState.LOADED) {
return renderWindow.textureManager.whiteTexture.texture
}
return texture
}
override fun forceRender(offset: Vec2i, consumer: GUIVertexConsumer, options: GUIVertexOptions?) {
consumer.addQuad(offset, offset + size, texture ?: return, uvStart, uvEnd, tint, options)
consumer.addQuad(offset, offset + size, getAvailableTexture(), uvStart, uvEnd, tint, options)
}
override fun forceSilentApply() = Unit

View File

@ -49,7 +49,7 @@ class TabListEntryElement(
private val background: ColorElement
private val skinElement = DynamicImageElement(guiRenderer, renderWindow.textureManager.getSkin(uuid, item.properties.textures), uvStart = Vec2(0.125), uvEnd = Vec2(0.25), size = Vec2i(8, 8))
private val skinElement = DynamicImageElement(guiRenderer, renderWindow.textureManager.getSkin(uuid, item.properties), uvStart = Vec2(0.125), uvEnd = Vec2(0.25), size = Vec2i(8, 8), parent = this)
// private val skinElement = ImageElement(guiRenderer, guiRenderer.renderWindow.textureManager.steveTexture, uvStart = Vec2(0.125), uvEnd = Vec2(0.25), size = Vec2i(512, 512))
private val nameElement = TextElement(guiRenderer, "", background = false, parent = this)
@ -86,7 +86,7 @@ class TabListEntryElement(
override fun forceRender(offset: Vec2i, consumer: GUIVertexConsumer, options: GUIVertexOptions?) {
background.render(offset, consumer, options)
skinElement.render(offset + Vec2i(PADDING, PADDING), consumer, options)
nameElement.render(offset + Vec2i(skinElement.size.x + PADDING, PADDING), consumer, options)
nameElement.render(offset + Vec2i(skinElement.size.x + PADDING * 3, PADDING), consumer, options)
pingElement.render(offset + Vec2i(HorizontalAlignments.RIGHT.getOffset(maxSize.x, pingElement.size.x + PADDING), PADDING), consumer, options)
}
@ -104,7 +104,7 @@ class TabListEntryElement(
nameElement.text = displayName
this.prefSize = Vec2i((PADDING * 3) + skinElement.prefSize.x + nameElement.prefSize.x + INNER_MARGIN + pingElement.prefSize.x, HEIGHT)
this.prefSize = Vec2i((PADDING * 6) + skinElement.prefSize.x + nameElement.prefSize.x + INNER_MARGIN + pingElement.prefSize.x, HEIGHT)
background.size = size
cacheUpToDate = false
}

View File

@ -19,5 +19,6 @@ import de.bixilon.minosoft.gui.rendering.system.base.shader.Shader
interface TextureArray {
fun load(latch: CountUpAndDownLatch)
fun activate()
fun use(shader: Shader, name: String = "uTextures")
}

View File

@ -15,9 +15,9 @@ package de.bixilon.minosoft.gui.rendering.system.base.texture
import de.bixilon.kotlinglm.vec2.Vec2
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.kutil.uuid.UUIDUtil.toUUID
import de.bixilon.minosoft.config.profile.profiles.account.AccountProfileManager
import de.bixilon.minosoft.data.player.properties.PlayerProperties
import de.bixilon.minosoft.data.player.properties.textures.PlayerTexture.Companion.isSteve
import de.bixilon.minosoft.data.player.properties.textures.PlayerTextures
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.gui.rendering.RenderConstants
import de.bixilon.minosoft.gui.rendering.gui.atlas.TextureLikeTexture
@ -56,14 +56,25 @@ abstract class TextureManager {
fun loadDefaultSkins(connection: PlayConnection) {
// ToDo: For testing purposes only, they will be moved to static textures
steveTexture = dynamicTextures.pushBuffer("3780a46b-a725-4b22-8366-01056c698386".toUUID()) { connection.assetsManager["minecraft:entity/steve".toResourceLocation().texture()].readTexture().second }
alexTexture = dynamicTextures.pushBuffer("3780a46b-a725-4b22-8366-01056c698386".toUUID()) { connection.assetsManager["minecraft:entity/alex".toResourceLocation().texture()].readTexture().second }
skin = getSkin(connection.player.uuid ?: UUID.randomUUID(), connection.account.properties?.textures)
steveTexture = dynamicTextures.pushBuffer(UUID(0L, 0L)) { connection.assetsManager["minecraft:entity/steve".toResourceLocation().texture()].readTexture().second }.apply { usages.incrementAndGet() }
alexTexture = dynamicTextures.pushBuffer(UUID(1L, 0L)) { connection.assetsManager["minecraft:entity/alex".toResourceLocation().texture()].readTexture().second }.apply { usages.incrementAndGet() }
skin = getSkin(connection.account.uuid, connection.account.properties).apply { usages.incrementAndGet() }
}
fun getSkin(uuid: UUID, properties: PlayerTextures?): DynamicTexture {
properties?.skin?.let { return dynamicTextures.pushRawArray(uuid) { it.read() } }
fun getSkin(uuid: UUID, properties: PlayerProperties?): DynamicTexture {
var properties = properties
if (properties == null) {
for (account in AccountProfileManager.selected.entries.values) {
if (account.uuid == uuid) {
properties = account.properties
}
}
if (properties == null) {
properties = PlayerProperties.fetch(uuid) // ToDo: async
}
}
properties.textures?.skin?.let { return dynamicTextures.pushRawArray(uuid) { it.read() } }
if (uuid.isSteve()) {
return steveTexture
}

View File

@ -20,4 +20,8 @@ import java.util.concurrent.atomic.AtomicInteger
interface DynamicTexture : ShaderIdentifiable {
val uuid: UUID
val usages: AtomicInteger
val state: DynamicTextureState
var onStateChange: (() -> Unit)?
}

View File

@ -0,0 +1,22 @@
/*
* Minosoft
* Copyright (C) 2020-2022 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.gui.rendering.system.base.texture.dynamic
enum class DynamicTextureState {
WAITING,
LOADING,
LOADED,
UNLOADED,
;
}

View File

@ -191,16 +191,26 @@ class OpenGLTextureArray(
state = TextureArrayStates.LOADED
}
override fun use(shader: Shader, name: String) {
shader.use()
override fun activate() {
for ((index, textureId) in textureIds.withIndex()) {
if (textureId == -1) {
continue
}
glActiveTexture(GL_TEXTURE0 + index)
glBindTexture(GL_TEXTURE_2D_ARRAY, textureId)
}
}
override fun use(shader: Shader, name: String) {
shader.use()
activate()
for ((index, textureId) in textureIds.withIndex()) {
if (textureId == -1) {
continue
}
glBindTexture(GL_TEXTURE_2D_ARRAY, textureId)
shader.setTexture("$name[$index]", index)
}
}

View File

@ -14,6 +14,7 @@
package de.bixilon.minosoft.gui.rendering.system.opengl.texture.dynamic
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTexture
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTextureState
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
@ -21,13 +22,23 @@ class OpenGLDynamicTexture(
override val uuid: UUID,
shaderId: Int,
) : DynamicTexture {
override var onStateChange: (() -> Unit)? = null
override val usages = AtomicInteger()
override var state: DynamicTextureState = DynamicTextureState.WAITING
set(value) {
field = value
onStateChange?.invoke()
}
override val shaderId: Int = shaderId
override var shaderId: Int = shaderId
get() {
if (usages.get() == 0) {
if (usages.get() == 0 || state == DynamicTextureState.UNLOADED) {
throw IllegalStateException("Texture was eventually garbage collected")
}
return field
}
override fun toString(): String {
return uuid.toString()
}
}

View File

@ -14,11 +14,13 @@
package de.bixilon.minosoft.gui.rendering.system.opengl.texture.dynamic
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.kutil.concurrent.pool.DefaultThreadPool
import de.bixilon.kutil.latch.CountUpAndDownLatch
import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.system.base.shader.Shader
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTexture
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTextureArray
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTextureState
import de.bixilon.minosoft.gui.rendering.system.opengl.texture.OpenGLTextureUtil
import org.lwjgl.opengl.GL11.*
import org.lwjgl.opengl.GL12.glTexImage3D
@ -54,6 +56,18 @@ class OpenGLDynamicTextureArray(
return pushBuffer(identifier) { ByteBuffer.wrap(data()) }
}
private fun load(texture: OpenGLDynamicTexture, index: Int, mipmaps: Array<ByteBuffer>) {
glBindTexture(GL_TEXTURE_2D_ARRAY, textureId)
for ((level, mipmap) in mipmaps.withIndex()) {
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, index, resolution shr level, resolution shr level, 1, GL_RGBA, GL_UNSIGNED_BYTE, mipmap)
}
texture.state = DynamicTextureState.LOADED
renderWindow.textureManager.staticTextures.activate()
}
@Synchronized
override fun pushBuffer(identifier: UUID, data: () -> ByteBuffer): OpenGLDynamicTexture {
check(textureId >= 0) { "Dynamic texture array not yet initialized!" }
cleanup()
@ -62,20 +76,19 @@ class OpenGLDynamicTextureArray(
return texture
}
}
val bytes = data()
check(bytes.limit() == resolution * resolution * 4) { "Texture must have a size of ${resolution}x${resolution}" }
val mipmaps = OpenGLTextureUtil.generateMipMaps(bytes, Vec2i(resolution, resolution))
val index = getNextIndex()
val texture = OpenGLDynamicTexture(identifier, createShaderIdentifier(index = index))
textures[index] = texture
texture.state = DynamicTextureState.LOADING
DefaultThreadPool += {
val bytes = data()
glBindTexture(GL_TEXTURE_2D_ARRAY, textureId)
check(bytes.limit() == resolution * resolution * 4) { "Texture must have a size of ${resolution}x${resolution}" }
for ((level, mipmap) in mipmaps.withIndex()) {
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, index, resolution shr level, resolution shr level, 1, GL_RGBA, GL_UNSIGNED_BYTE, mipmap)
val mipmaps = OpenGLTextureUtil.generateMipMaps(bytes, Vec2i(resolution, resolution))
renderWindow.queue += { load(texture, index, mipmaps) }
}
return OpenGLDynamicTexture(identifier, createShaderIdentifier(index = index))
return texture
}
private fun createShaderIdentifier(array: Int = this.index, index: Int): Int {
@ -96,11 +109,16 @@ class OpenGLDynamicTextureArray(
this.textureId = textureId
}
override fun activate() {
glActiveTexture(GL_TEXTURE0 + index)
glBindTexture(GL_TEXTURE_2D_ARRAY, textureId)
}
override fun use(shader: Shader, name: String) {
shader.use()
glActiveTexture(GL_TEXTURE0 + index)
glBindTexture(GL_TEXTURE_2D_ARRAY, textureId)
activate()
shader.setTexture("$name[$index]", index)
}

View File

@ -648,6 +648,7 @@ class WorldRenderer(
}
override fun prepareDraw() {
renderWindow.textureManager.staticTextures.use(shader)
if (clearVisibleNextFrame) {
visible.clear()
clearVisibleNextFrame = false

View File

@ -102,7 +102,11 @@ class TabListS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
// item not yet created
return@run null
}
val item = TabListItem(name = data.name)
val item = if (entity === connection.player) {
connection.player.tabListItem
} else {
TabListItem(name = data.name)
}
connection.tabList.tabListItemsByUUID[uuid] = item
connection.tabList.tabListItemsByName[data.name] = item