mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-18 03:44:54 -04:00
load dynamic textures async, fix some tab list bugs
This commit is contained in:
parent
e2df230da4
commit
e7f3891cc4
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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?,
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
) {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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?,
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)?
|
||||
}
|
||||
|
@ -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,
|
||||
;
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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 index = getNextIndex()
|
||||
val texture = OpenGLDynamicTexture(identifier, createShaderIdentifier(index = index))
|
||||
textures[index] = texture
|
||||
texture.state = DynamicTextureState.LOADING
|
||||
DefaultThreadPool += {
|
||||
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()
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -648,6 +648,7 @@ class WorldRenderer(
|
||||
}
|
||||
|
||||
override fun prepareDraw() {
|
||||
renderWindow.textureManager.staticTextures.use(shader)
|
||||
if (clearVisibleNextFrame) {
|
||||
visible.clear()
|
||||
clearVisibleNextFrame = false
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user