From 5dd7fa292d7e635ac1660362b3b82049a80b85c3 Mon Sep 17 00:00:00 2001 From: meehl <12648828+meehl@users.noreply.github.com> Date: Sun, 30 Aug 2026 10:26:12 +0200 Subject: [PATCH] Cache tooltip assets This solves two problems: - Item influence icons were being allocated and loaded on **every** frame while the tooltip was open. - Every tooltip was allocating and loading its own header and separator images, even though most of them share the same images. This change significantly reduces the number of disk loads and GPU texture allocations/frees. As a consequence, tooltip assets now stay loaded and do not get freed. This is fine because they're fairly small and commonly used anyway. --- src/Classes/Tooltip.lua | 165 +++++++++++++++++++++------------------- 1 file changed, 86 insertions(+), 79 deletions(-) diff --git a/src/Classes/Tooltip.lua b/src/Classes/Tooltip.lua index 0a806c0ced..5111038f6c 100644 --- a/src/Classes/Tooltip.lua +++ b/src/Classes/Tooltip.lua @@ -40,6 +40,84 @@ for _, recipeName in pairs(recipeNames) do recipeImages[recipeName]:Load("TreeData/" .. recipeName .. ".png", "CLAMP") end + +-- spell-checker: disable +local headerConfigs = { + RELIC = {left="Assets/itemsheaderfoilleft.png",middle="Assets/itemsheaderfoilmiddle.png",right="Assets/itemsheaderfoilright.png",height=54,sideWidth=47,middleWidth=52,textYOffset=3,allowInfluenceIcon=true}, + UNIQUE = {left="Assets/itemsheaderuniqueleft.png",middle="Assets/itemsheaderuniquemiddle.png",right="Assets/itemsheaderuniqueright.png",height=54,sideWidth=47,middleWidth=52,textYOffset=3,allowInfluenceIcon=true}, + RARE = {left="Assets/itemsheaderrareleft.png",middle="Assets/itemsheaderraremiddle.png",right="Assets/itemsheaderrareright.png",height=54,sideWidth=47,middleWidth=52,textYOffset=3,allowInfluenceIcon=true}, + MAGIC = {left="Assets/itemsheadermagicleft.png",middle="Assets/itemsheadermagicmiddle.png",right="Assets/itemsheadermagicright.png",height=38,sideWidth=32,middleWidth=32,textYOffset=6,allowInfluenceIcon=true}, + NORMAL = {left="Assets/itemsheaderwhiteleft.png",middle="Assets/itemsheaderwhitemiddle.png",right="Assets/itemsheaderwhiteright.png",height=38,sideWidth=32,middleWidth=32,textYOffset=6,allowInfluenceIcon=true}, + GEM = {left="Assets/itemsheadergemleft.png",middle="Assets/itemsheadergemmiddle.png",right="Assets/itemsheadergemright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=5}, + JEWEL = {left="Assets/jewelpassiveheaderleft.png",middle="Assets/jewelpassiveheadermiddle.png",right="Assets/jewelpassiveheaderright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=6}, + NOTABLE = {left="Assets/notablepassiveheaderleft.png",middle="Assets/notablepassiveheadermiddle.png",right="Assets/notablepassiveheaderright.png",height=38,sideWidth=38,middleWidth=38,textYOffset=6}, + PASSIVE = {left="Assets/normalpassiveheaderleft.png",middle="Assets/normalpassiveheadermiddle.png",right="Assets/normalpassiveheaderright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=6}, + KEYSTONE = {left="Assets/keystonepassiveheaderleft.png",middle="Assets/keystonepassiveheadermiddle.png",right="Assets/keystonepassiveheaderright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=6}, + ASCENDANCY = {left="Assets/ascendancypassiveheaderleft.png",middle="Assets/ascendancypassiveheadermiddle.png",right="Assets/ascendancypassiveheaderright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=6}, + MASTERY = {left="Assets/masteryheaderunallocatedleft.png",middle="Assets/masteryheaderunallocatedmiddle.png",right="Assets/masteryheaderunallocatedright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=6}, + MASTERYALLOC = {left="Assets/masteryheaderallocatedleft.png",middle="Assets/masteryheaderallocatedmiddle.png",right="Assets/masteryheaderallocatedright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=6}, +} +local headerInfluence = { + Fractured = "Assets/fracturedicon.png", + Veiled = "Assets/veiledicon.png", + Shaper = "Assets/shapericon.png", + Elder = "Assets/eldericon.png", + Redeemer = "Assets/redeemericon.png", + Hunter = "Assets/huntericon.png", + Crusader = "Assets/crusadericon.png", + Warlord = "Assets/warlordicon.png", + Eater = "Assets/eatericon.png", + Exarch = "Assets/exarchicon.png", + Synthesis = "Assets/synthesisicon.png", + Experimented = "Assets/experimentedicon.png", + Foulborn = "Assets/breachicon.png", + Memory = "Assets/memoryicon.png", + Vestigial = "Assets/vestigialicon.png", +} +local separatorConfigs = { + RELIC = "Assets/itemsseparatorfoil.png", + UNIQUE = "Assets/itemsseparatorunique.png", + RARE = "Assets/itemsseparatorrare.png", + MAGIC = "Assets/itemsseparatormagic.png", + NORMAL = "Assets/itemsseparatorwhite.png", + GEM = "Assets/itemsseparatorgem.png", +} +-- spell-checker: enable + +-- Cache tooltip assets +local tooltipAssetCache = { + header = {}, + influence = {}, + separator = {}, +} + +local function getCachedImage(cache, key, path) + local image = cache[key] + + if image == nil then + image = NewImageHandle() + image:Load(path) + cache[key] = image + end + + return image +end + +local function getHeaderImage(rarity, location) + local key = rarity .. ":" .. location + return getCachedImage(tooltipAssetCache.header, key, headerConfigs[rarity][location]) +end + +local function getInfluenceIconImage(influence) + return getCachedImage(tooltipAssetCache.influence, influence, headerInfluence[influence]) +end + +local function getSeparatorImage(rarity) + local path = separatorConfigs[rarity] or separatorConfigs["NORMAL"] + return getCachedImage(tooltipAssetCache.separator, rarity, path) +end + + ---@class Tooltip local TooltipClass = newClass("Tooltip") @@ -130,25 +208,7 @@ function TooltipClass:AddSeparator(size) if self.tooltipHeader then local rarity = tostring(self.tooltipHeader):upper() - -- spell-checker: disable - local separatorConfigs = { - RELIC = "Assets/itemsseparatorfoil.png", - UNIQUE = "Assets/itemsseparatorunique.png", - RARE = "Assets/itemsseparatorrare.png", - MAGIC = "Assets/itemsseparatormagic.png", - NORMAL = "Assets/itemsseparatorwhite.png", - GEM = "Assets/itemsseparatorgem.png", - } - -- spell-checker: enable - local separatorPath = separatorConfigs[rarity] or separatorConfigs.NORMAL - - if not self.separatorImage or self.separatorImagePath ~= separatorPath then - self.separatorImage = NewImageHandle() - self.separatorImage:Load(separatorPath) - self.separatorImagePath = separatorPath - end - - separatorImage = self.separatorImage + separatorImage = getSeparatorImage(rarity) end local lastBlock = lastLine and lastLine.block or 1 @@ -297,7 +357,7 @@ function TooltipClass:CalculateColumns(ttY, ttX, ttH, ttW, viewPort) end local lineX = lineCentered and (x + ttW / 2) or (x + (H_PAD / 2)) local lineAlign = lineCentered and "CENTER_X" or "LEFT" - + local stackEntry = {lineX, y, lineAlign, data.size, font, data.text} if data.modLine and data.modLine.disabled then stackEntry.strikethrough = true @@ -390,40 +450,6 @@ function TooltipClass:Draw(x, y, w, h, viewPort) ttW = titleW + 50 end end - -- spell-checker: disable - local headerInfluence = { - Fractured = "Assets/fracturedicon.png", - Veiled = "Assets/veiledicon.png", - Shaper = "Assets/shapericon.png", - Elder = "Assets/eldericon.png", - Redeemer = "Assets/redeemericon.png", - Hunter = "Assets/huntericon.png", - Crusader = "Assets/crusadericon.png", - Warlord = "Assets/warlordicon.png", - Eater = "Assets/eatericon.png", - Exarch = "Assets/exarchicon.png", - Synthesis = "Assets/synthesisicon.png", - Experimented = "Assets/experimentedicon.png", - Foulborn = "Assets/breachicon.png", - Memory = "Assets/memoryicon.png", - Vestigial = "Assets/vestigialicon.png", - } - local headerConfigs = { - RELIC = {left="Assets/itemsheaderfoilleft.png",middle="Assets/itemsheaderfoilmiddle.png",right="Assets/itemsheaderfoilright.png",height=54,sideWidth=47,middleWidth=52,textYOffset=3,allowInfluenceIcon=true}, - UNIQUE = {left="Assets/itemsheaderuniqueleft.png",middle="Assets/itemsheaderuniquemiddle.png",right="Assets/itemsheaderuniqueright.png",height=54,sideWidth=47,middleWidth=52,textYOffset=3,allowInfluenceIcon=true}, - RARE = {left="Assets/itemsheaderrareleft.png",middle="Assets/itemsheaderraremiddle.png",right="Assets/itemsheaderrareright.png",height=54,sideWidth=47,middleWidth=52,textYOffset=3,allowInfluenceIcon=true}, - MAGIC = {left="Assets/itemsheadermagicleft.png",middle="Assets/itemsheadermagicmiddle.png",right="Assets/itemsheadermagicright.png",height=38,sideWidth=32,middleWidth=32,textYOffset=6,allowInfluenceIcon=true}, - NORMAL = {left="Assets/itemsheaderwhiteleft.png",middle="Assets/itemsheaderwhitemiddle.png",right="Assets/itemsheaderwhiteright.png",height=38,sideWidth=32,middleWidth=32,textYOffset=6,allowInfluenceIcon=true}, - GEM = {left="Assets/itemsheadergemleft.png",middle="Assets/itemsheadergemmiddle.png",right="Assets/itemsheadergemright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=5}, - JEWEL = {left="Assets/jewelpassiveheaderleft.png",middle="Assets/jewelpassiveheadermiddle.png",right="Assets/jewelpassiveheaderright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=6}, - NOTABLE = {left="Assets/notablepassiveheaderleft.png",middle="Assets/notablepassiveheadermiddle.png",right="Assets/notablepassiveheaderright.png",height=38,sideWidth=38,middleWidth=38,textYOffset=6}, - PASSIVE = {left="Assets/normalpassiveheaderleft.png",middle="Assets/normalpassiveheadermiddle.png",right="Assets/normalpassiveheaderright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=6}, - KEYSTONE = {left="Assets/keystonepassiveheaderleft.png",middle="Assets/keystonepassiveheadermiddle.png",right="Assets/keystonepassiveheaderright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=6}, - ASCENDANCY = {left="Assets/ascendancypassiveheaderleft.png",middle="Assets/ascendancypassiveheadermiddle.png",right="Assets/ascendancypassiveheaderright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=6}, - MASTERY = {left="Assets/masteryheaderunallocatedleft.png",middle="Assets/masteryheaderunallocatedmiddle.png",right="Assets/masteryheaderunallocatedright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=6}, - MASTERYALLOC = {left="Assets/masteryheaderallocatedleft.png",middle="Assets/masteryheaderallocatedmiddle.png",right="Assets/masteryheaderallocatedright.png",height=38,sideWidth=33,middleWidth=38,textYOffset=6}, - } - -- spell-checker: enable local config if self.tooltipHeader and main.showFlavourText and self.lines[1] and self.lines[1].text then local rarity = tostring(self.tooltipHeader):upper() @@ -483,18 +509,6 @@ function TooltipClass:Draw(x, y, w, h, viewPort) self.titleYOffset = config.textYOffset or 0 - if not self.headerLeft or self.headerLeftPath ~= config.left then - self.headerLeft = NewImageHandle() - self.headerLeft:Load(config.left) - self.headerLeftPath = config.left - self.headerMiddle = NewImageHandle() - self.headerMiddle:Load(config.middle) - self.headerMiddlePath = config.middle - self.headerRight = NewImageHandle() - self.headerRight:Load(config.right) - self.headerRightPath = config.right - end - local headerHeight = config.height local headerSideWidth = config.sideWidth local headerMiddleWidth = config.middleWidth @@ -504,13 +518,6 @@ function TooltipClass:Draw(x, y, w, h, viewPort) local headerTotalWidth = ttW - 2 * BORDER_WIDTH local headerMiddleAreaWidth = m_max(0, headerTotalWidth - 2 * headerSideWidth) - if self.influenceHeader1 then - self.influenceIcon1 = NewImageHandle() - self.influenceIcon1:Load(headerInfluence[self.influenceHeader1]) - self.influenceIcon2 = NewImageHandle() - self.influenceIcon2:Load(headerInfluence[self.influenceHeader2]) - end - local foilTypes = { ["Rainbow"] = {0.6, 1, 0.5}, ["Amethyst"] = {0.9, 0.6, 1}, @@ -536,10 +543,10 @@ function TooltipClass:Draw(x, y, w, h, viewPort) end end -- Draw left cap first, then influence icon on top - DrawImage(self.headerLeft, headerX, headerY, headerSideWidth, headerHeight) + DrawImage(getHeaderImage(rarity, "left"), headerX, headerY, headerSideWidth, headerHeight) if self.influenceHeader1 and config.allowInfluenceIcon then SetDrawColor(1, 1, 1) - DrawImage(self.influenceIcon1, headerX + 2, headerY + (headerHeight - (headerHeight/2))/2, headerHeight/2, headerHeight/2) + DrawImage(getInfluenceIconImage(self.influenceHeader1), headerX + 2, headerY + (headerHeight - (headerHeight/2))/2, headerHeight/2, headerHeight/2) end if self.tooltipHeader == "RELIC" then @@ -556,20 +563,20 @@ function TooltipClass:Draw(x, y, w, h, viewPort) local drawX = headerX + headerSideWidth local endX = headerX + headerTotalWidth - headerSideWidth while drawX + headerMiddleWidth <= endX do - DrawImage(self.headerMiddle, drawX, headerY, headerMiddleWidth, headerHeight) + DrawImage(getHeaderImage(rarity, "middle"), drawX, headerY, headerMiddleWidth, headerHeight) drawX = drawX + headerMiddleWidth end local remainingWidth = endX - drawX if remainingWidth > 0 then - DrawImage(self.headerMiddle, drawX, headerY, remainingWidth, headerHeight) + DrawImage(getHeaderImage(rarity, "middle"), drawX, headerY, remainingWidth, headerHeight) end end -- Draw right cap - DrawImage(self.headerRight, headerX + headerTotalWidth - headerSideWidth, headerY, headerSideWidth, headerHeight) + DrawImage(getHeaderImage(rarity, "right"), headerX + headerTotalWidth - headerSideWidth, headerY, headerSideWidth, headerHeight) if self.influenceHeader2 and config.allowInfluenceIcon then SetDrawColor(1, 1, 1) - DrawImage(self.influenceIcon2, headerX + headerTotalWidth - (headerHeight/2) - 2, headerY + (headerHeight - (headerHeight/2))/2, headerHeight/2, headerHeight/2) + DrawImage(getInfluenceIconImage(self.influenceHeader2), headerX + headerTotalWidth - (headerHeight/2) - 2, headerY + (headerHeight - (headerHeight/2))/2, headerHeight/2, headerHeight/2) end end