perf(render): Yields rendered from a dynamically created texture

This commit is contained in:
yairm210 2025-01-23 17:45:29 +02:00
parent af80570b25
commit 6234171a55
4 changed files with 55 additions and 17 deletions

View File

@ -116,8 +116,12 @@ object FontRulesetIcons {
fun getPixmapFromActor(actor: Actor): Pixmap {
val (boxWidth, boxHeight) = scaleAndPositionActor(actor)
return getPixmapFromActorBase(actor, boxWidth, boxHeight)
}
// Also required for dynamically generating pixmaps for pixmappacker
fun getPixmapFromActorBase(actor: Actor, boxWidth: Int, boxHeight: Int): Pixmap {
val pixmap = Pixmap(boxWidth, boxHeight, Pixmap.Format.RGBA8888)
frameBuffer.begin()
Gdx.gl.glClearColor(0f,0f,0f,0f)

View File

@ -4,12 +4,14 @@ import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.Batch
import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup
import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.models.stats.Stats
import com.unciv.models.translations.tr
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.components.extensions.addToCenter
import com.unciv.ui.components.extensions.setSize
import com.unciv.ui.components.extensions.surroundWithCircle
import com.unciv.ui.components.extensions.toLabel
@ -24,6 +26,7 @@ class YieldGroup : HorizontalGroup() {
if (currentStats.equals(stats)) return // don't need to update - this is a memory and time saver!
currentStats = stats
clearChildren()
for ((stat, amount) in stats) {
if (amount > 0f) // Defense against upstream bugs - negatives would show as "lots"
addActor(getStatIconsTable(stat.name, amount.toInt()))
@ -32,33 +35,31 @@ class YieldGroup : HorizontalGroup() {
}
fun getIcon(statName: String) =
ImageGetter.getStatIcon(statName).surroundWithCircle(12f)
.apply { circle.color = ImageGetter.CHARCOAL; circle.color.a = 0.5f }
Image(ImageGetter.getStatWithBackground(statName))
private fun getStatIconsTable(statName: String, number: Int): Table {
val table = Table()
when (number) {
1 -> table.add(getIcon(statName))
1 -> table.add(getIcon(statName)).size(12f)
2 -> {
table.add(getIcon(statName)).row()
table.add(getIcon(statName))
table.add(getIcon(statName)).size(12f).row()
table.add(getIcon(statName)).size(12f)
}
3 -> {
table.add(getIcon(statName)).colspan(2).row()
table.add(getIcon(statName))
table.add(getIcon(statName))
table.add(getIcon(statName)).size(12f).colspan(2).row()
table.add(getIcon(statName)).size(12f)
table.add(getIcon(statName)).size(12f)
}
4 -> {
table.add(getIcon(statName))
table.add(getIcon(statName)).row()
table.add(getIcon(statName))
table.add(getIcon(statName))
table.add(getIcon(statName)).size(12f)
table.add(getIcon(statName)).size(12f).row()
table.add(getIcon(statName)).size(12f)
table.add(getIcon(statName)).size(12f)
}
else -> {
val group = Group().apply { setSize(22f, 22f) }
val largeImage = ImageGetter.getStatIcon(statName).surroundWithCircle(22f)
.apply { circle.color = ImageGetter.CHARCOAL;circle.color.a = 0.5f }
val largeImage = getIcon(statName).apply { setSize(22f) }
group.addToCenter(largeImage)
if (number > 5) {

View File

@ -3,8 +3,8 @@ package com.unciv.ui.images
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.Batch
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.utils.Align
import com.unciv.ui.components.NonTransformGroup
import com.unciv.ui.components.extensions.center
open class IconCircleGroup(
@ -13,7 +13,11 @@ open class IconCircleGroup(
resizeActor: Boolean = true,
color: Color = Color.WHITE,
circleImage: String = "OtherIcons/Circle"
): NonTransformGroup() {
): Group() { // can't be nonTransformGroup because we need to dynamically pack yield images
init {
isTransform = false
}
val circle = ImageGetter.getImage(circleImage).apply {
setSize(size, size)

View File

@ -3,10 +3,12 @@ package com.unciv.ui.images
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.Texture.TextureFilter
import com.badlogic.gdx.graphics.g2d.Batch
import com.badlogic.gdx.graphics.g2d.NinePatch
import com.badlogic.gdx.graphics.g2d.PixmapPacker
import com.badlogic.gdx.graphics.g2d.TextureAtlas
import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.badlogic.gdx.scenes.scene2d.Actor
@ -25,6 +27,7 @@ import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.nation.Nation
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.skins.SkinCache
import com.unciv.models.stats.Stat
import com.unciv.models.tilesets.TileSetCache
import com.unciv.ui.components.NonTransformGroup
import com.unciv.ui.components.extensions.center
@ -54,6 +57,30 @@ object ImageGetter {
lateinit var atlas: TextureAtlas
private val atlases = HashMap<String, TextureAtlas>()
var ruleset = Ruleset()
// Performance improvement - "pack" the stat images together with the circle to the same texture
// This allows modded stat icons to not require texture swaps when rendering leading to it being ~50x faster
private var yieldPixmapPacker = PixmapPacker(2048, 2048, Pixmap.Format.RGBA8888, 2, false).apply { packToTexture = true }
private var yieldAtlas = yieldPixmapPacker.generateTextureAtlas(TextureFilter.MipMapLinearLinear, TextureFilter.MipMapLinearLinear, true)
fun getStatWithBackground(statName: String): TextureRegionDrawable? {
if (textureRegionDrawables.containsKey(statName)) return textureRegionDrawables[statName]
for (stat in Stat.entries) { // pack all images
val actor =
getStatIcon(stat.name).surroundWithCircle(100f)
.apply { circle.color = CHARCOAL; circle.color.a = 0.5f }
.apply { isTransform = true; setScale(1f, -1f); setPosition(0f, height) } // flip Y axis
// By flipping the y axis when *generating the pixmap* we can ensure that when *rendering* we can have isTransform=false :)
yieldPixmapPacker.pack(stat.name, FontRulesetIcons.getPixmapFromActorBase(actor, 100, 100))
}
yieldAtlas = yieldPixmapPacker.generateTextureAtlas(TextureFilter.MipMapLinearLinear, TextureFilter.MipMapLinearLinear, true)
for (region in yieldAtlas.regions) {
val drawable = TextureRegionDrawable(region)
textureRegionDrawables[region.name] = drawable
}
return textureRegionDrawables[statName]
}
// We then shove all the drawables into a hashmap, because the atlas specifically tells us
// that the search on it is inefficient
@ -65,6 +92,8 @@ object ImageGetter {
fun resetAtlases() {
atlases.values.forEach { it.dispose() }
atlases.clear()
yieldPixmapPacker.dispose()
yieldPixmapPacker = PixmapPacker(2048, 2048, Pixmap.Format.RGBA8888, 2, false).apply { packToTexture = true }
}
fun reloadImages() = setNewRuleset(ruleset)