Thread Tools Display Modes
06-28-13, 09:42 AM   #1
zork
A Pyroguard Emberseer
 
zork's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2008
Posts: 1,740
Nameplate FPS drop research

I'm currently investigating nameplates on patch 5.4 ptr.

Default nameplates:


rDiabloPlates enabled:


rDiabloPlates enabled ... everything removed, healthbar texture replaced


So actually I gain fps on the last screenshot. I think the fps drop is not an issue with the nameplate addons in general. I think it has sth to do with how many objects you create etc.

So one has to be very carefully when building nameplate addons. They can eat your fps alive.

Currently investigating all that stuff a little bit more.
__________________
| Simple is beautiful.
| WoWI AddOns | GitHub | Zork (WoW)

"I wonder what the non-pathetic people are doing tonight?" - Rajesh Koothrappali (The Big Bang Theory)
  Reply With Quote
06-28-13, 10:22 AM   #2
Haleth
This Space For Rent
 
Haleth's Avatar
Featured
Join Date: Sep 2008
Posts: 1,173
I'll look into this. Something else I've noticed on the last couple of patches though - when you have certain UI panels open, like the friends list, even without addons your fps can drop dramatically for no apparent reason. Ever seen that?
  Reply With Quote
06-28-13, 06:13 PM   #3
semlar
A Pyroguard Emberseer
 
semlar's Avatar
AddOn Author - Click to view addons
Join Date: Sep 2007
Posts: 1,060
I ran into this in my map project when I tried to draw very large numbers of textures and font strings at once. Just having them on-screen caused a noticeable dip in the frame rate, but attempting to move the frame they were attached to practically froze the game.

I had created many font strings as labels for various POIs on the map..


And displaying them for the entire continent greatly impacted frame rates.


However, since you could both drag and zoom the map, it quickly became obvious that moving or scaling the frame caused exponentially more of a problem for FPS.

When I adjusted the script to hide the labels while dragging, and showing them when finished, it completely solved the problem. As long as the regions aren't visible while they're being repositioned they have almost no impact on the frame rate.

This leads me to believe that simply recalculating the positions of the regions isn't the problem, but redrawing them every time they move is. Therefor, if you can limit how often they're redrawn you should be able to limit the impact on frame rate.

This may not make any sense, but instead of anchoring your nameplates directly to the existing one, try attaching them to the WorldFrame and using an OnUpdate script to reposition them only once per frame.

The author of kui nameplates uses this method to keep their nameplates pixel perfect, although an unexpected side effect is higher frame rates.
  Reply With Quote
06-28-13, 06:38 PM   #4
Haleth
This Space For Rent
 
Haleth's Avatar
Featured
Join Date: Sep 2008
Posts: 1,173
That's a very interesting read. However...

Originally Posted by semlar View Post
This may not make any sense, but instead of anchoring your nameplates directly to the existing one, try attaching them to the WorldFrame and using an OnUpdate script to reposition them only once per frame.
Isn't once per frame refresh already the speed at which frames and regions are repositioned/redrawn? I don't see how dumping the positioning into a separate OnUpdate script will improve the situation.

Unless, of course, all UI code normally runs on the same thread, and only by using OnUpdate can you allow proper multi-threading...

Or perhaps frame/region positions might simply not be calculated at all when the object is hidden.

Last edited by Haleth : 06-28-13 at 06:43 PM.
  Reply With Quote
06-29-13, 03:16 AM   #5
semlar
A Pyroguard Emberseer
 
semlar's Avatar
AddOn Author - Click to view addons
Join Date: Sep 2007
Posts: 1,060
Alright, I wrote a small proof of concept that demonstrates just how much of a difference hiding the frame before you reposition it makes, even if it makes no visible difference in-game.
Lua Code:
  1. local Nameplates, PreviousWorldChildren = {}, 0 -- Table to hold our list of detected nameplates
  2. WorldFrame:HookScript('OnUpdate', function(self)
  3.     local currentWorldChildren = self:GetNumChildren()
  4.     if currentWorldChildren ~= PreviousWorldChildren then PreviousWorldChildren = currentWorldChildren
  5.         for _, plate in pairs({self:GetChildren()}) do
  6.             if not Nameplates[plate] then -- Check if it's already been seen
  7.                 local name = plate:GetName()
  8.                 if name and strmatch(name, '^NamePlate%d+$') then -- Test that it's a nameplate
  9.                     Nameplates[plate] = CreateFrame('frame', nil, WorldFrame) -- Record our new frame
  10.                     for i = 1, 500 do -- Make a bunch of regions for a stress test
  11.                         local fs = Nameplates[plate]:CreateFontString(nil, nil, 'GameFontNormalHuge')
  12.                         fs:SetText('LAG')
  13.                         fs:SetPoint('CENTER', math.random(-50,50), math.random(-12,12)) -- This is just to make it look more interesting
  14.                     end
  15.                 end
  16.             end
  17.         end
  18.     end
  19.     for plate, f in pairs(Nameplates) do -- Iterate over existing nameplates every frame
  20.         if plate:IsShown() then -- Check visibility
  21.             f:Hide() -- Try commenting this out and see what happens to your frame rate
  22.             f:SetSize(plate:GetSize()) -- This isn't necessary to call every update but it simplifies the example
  23.             f:SetPoint('CENTER', WorldFrame, 'BOTTOMLEFT', plate:GetCenter()) -- Position our frame relative to the nameplate
  24.             f:Show()
  25.         else -- Hide if nameplate is hidden
  26.             f:Hide()
  27.         end
  28.     end
  29. end)

You can forget about repositioning the frame OnUpdate without hiding it first, it doesn't seem to make as much of a difference as I thought. On the other hand, this trick seems to make a huge difference.

The only downside is it lags one frame behind the default nameplate, but maybe there's a way to overcome that.

There are 2000 font strings per nameplate in this screenshot, and while it has dropped my frame rate down to around 15 fps I can still smoothly rotate the camera and run around without any issues. Attaching them directly to the nameplate completely freezes the game in this same test.

Last edited by semlar : 08-21-13 at 06:17 AM.
  Reply With Quote
06-29-13, 03:46 AM   #6
zork
A Pyroguard Emberseer
 
zork's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2008
Posts: 1,740
Thanks semlar. The difference in FPS is hilarious btw. You can even see how the fontstrings move point-by-point.

I never used an OnUpdate script that runs on every frame before though.



So semlars solution to this is:

Instead of putting all your frames on the nameplate itself we create a new frame on the Worldframe that still has a link to the corresponding Blizzard nameplate. On every frame update we hide our plate first, do our calculations and show it thereafter.

Two things in his example will lag you out by a mile.
  • Adding frames directly to the Blizzard nameplate
  • Not hiding our new nameplate frame before doing calculations

If you want the fastest nameplates on the planet you need to build them like semlar posted.
To make it clear... on the screenshot are 500 fontstrings per nameplate. Not 5. And it still runs smoothly.

This also has a major bonus. Since we are living in the environment of OnUpdate we can do all the calculations we want. (color calculation etc.)

Thanks alot semlar that was exactly what I was looking for.

Sidepoint...if some needs the current nameplate regions. Here we go:

Lua Code:
  1. --do dat
  2.     local f = NamePlate1
  3.     f.barFrame, f.nameFrame = f:GetChildren()
  4.     f.healthbar, f.castbar = f.barFrame:GetChildren()
  5.     f.threat, f.border, f.highlight, f.level, f.boss, f.raid, f.dragon = f.barFrame:GetRegions()
  6.     f.name = f.nameFrame:GetRegions()
  7.     f.healthbar.texture = f.healthbar:GetRegions()
  8.     f.castbar.texture, f.castbar.border, f.castbar.shield, f.castbar.icon, f.castbar.name, f.castbar.nameShadow = f.castbar:GetRegions()
__________________
| Simple is beautiful.
| WoWI AddOns | GitHub | Zork (WoW)

"I wonder what the non-pathetic people are doing tonight?" - Rajesh Koothrappali (The Big Bang Theory)

Last edited by zork : 06-29-13 at 04:38 AM.
  Reply With Quote
08-21-13, 02:04 AM   #7
zork
A Pyroguard Emberseer
 
zork's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2008
Posts: 1,740
I case anyone is interested. Here is my test addon using semlars approach.

I recycled as much frames as I could.

Lua Code:
  1. --if 1 == 1 then return end
  2.  
  3.     -----------------------------------------
  4.     -- CONFIG
  5.     -----------------------------------------
  6.  
  7.     local cfg = {}
  8.  
  9.     cfg.width = 100
  10.     cfg.height = 26
  11.  
  12.     -----------------------------------------
  13.     -- VARIABLES
  14.     -----------------------------------------
  15.  
  16.     --add a new global frame
  17.     local RDP = CreateFrame("Frame", "PlateCollector", WorldFrame)
  18.     RDP:SetAllPoints()
  19.     RDP.nameplates = {}
  20.  
  21.     local RAID_CLASS_COLORS = RAID_CLASS_COLORS
  22.     local FACTION_BAR_COLORS = FACTION_BAR_COLORS
  23.     local unpack = unpack
  24.  
  25.     -----------------------------------------
  26.     -- FUNCTIONS
  27.     -----------------------------------------
  28.  
  29.     local function RGBPercToHex(r, g, b)
  30.       r = r <= 1 and r >= 0 and r or 0
  31.       g = g <= 1 and g >= 0 and g or 0
  32.       b = b <= 1 and b >= 0 and b or 0
  33.       return string.format("%02x%02x%02x", r*255, g*255, b*255)
  34.     end
  35.  
  36.     local function FixColor(color)
  37.       color.r,color.g,color.b = floor(color.r*100+.5)/100, floor(color.g*100+.5)/100, floor(color.b*100+.5)/100
  38.     end
  39.  
  40.     --NamePlateOnShow func
  41.     local function NamePlateOnShow(self)
  42.       --new plate
  43.       local newPlate = self.healthbar:GetParent()
  44.       --hide level
  45.       self.level:Hide()
  46.       --healthbar
  47.       self.healthbar:ClearAllPoints()
  48.       self.healthbar:SetPoint("TOP", newPlate)
  49.       self.healthbar:SetPoint("LEFT", newPlate)
  50.       self.healthbar:SetPoint("RIGHT", newPlate)
  51.       self.healthbar:SetHeight(newPlate.height/2)
  52.       --name
  53.       self.name:ClearAllPoints()
  54.       self.name:SetPoint("BOTTOM", newPlate, "TOP")
  55.       self.name:SetPoint("LEFT", newPlate,-10,0)
  56.       self.name:SetPoint("RIGHT", newPlate,10,0)
  57.       self.name:SetFont(STANDARD_TEXT_FONT,11,"THINOUTLINE")
  58.       self.realName = self.name:GetText()
  59.     end
  60.  
  61.     --NamePlateCastbarOnShow func
  62.     local function NamePlateCastbarOnShow(self)
  63.         --new plate
  64.         local newPlate = self:GetParent()
  65.         --castbar
  66.         self:ClearAllPoints()
  67.         self:SetPoint("BOTTOM",newPlate)
  68.         self:SetPoint("LEFT",newPlate)
  69.         self:SetPoint("RIGHT",newPlate)
  70.         self:SetHeight(newPlate.height/2)
  71.         --castbar icon
  72.         self.icon:ClearAllPoints()
  73.         self.icon:SetPoint("RIGHT",newPlate, "LEFT")
  74.     end
  75.  
  76.     --NamePlateNameUpdate func
  77.     local function NamePlateNameUpdate(self)
  78.       if not self.realName then return end
  79.       local cs = RGBPercToHex(self.level:GetTextColor())
  80.       --name string
  81.       local name = self.realName
  82.       local level = self.level:GetText()
  83.       if self.boss:IsShown() == 1 then
  84.         level = "??"
  85.         cs = "ff6600"
  86.       elseif self.dragon:IsShown() == 1 then
  87.         level = level.."+"
  88.       end
  89.       self.name:SetText("|cff"..cs..""..level.."|r "..name)
  90.       self.level:Hide()
  91.     end
  92.  
  93.     --NamePlateHealthBarUpdate func
  94.     local function NamePlateHealthBarUpdate(self)
  95.       local color = {}
  96.       if self.threat:IsShown() then
  97.         color.r, color.g, color.b = self.threat:GetVertexColor()
  98.         FixColor(color)
  99.         if color.r+color.g+color.b < 3 then
  100.           self.healthbar:SetStatusBarColor(color.r,color.g,color.b)
  101.           return
  102.         end
  103.       end
  104.       color.r, color.g, color.b = self.healthbar:GetStatusBarColor()
  105.       FixColor(color)
  106.       for class, _ in pairs(RAID_CLASS_COLORS) do
  107.         if RAID_CLASS_COLORS[class].r == color.r and RAID_CLASS_COLORS[class].g == color.g and RAID_CLASS_COLORS[class].b == color.b then
  108.           self.healthbar:SetStatusBarColor(RAID_CLASS_COLORS[class].r,RAID_CLASS_COLORS[class].g,RAID_CLASS_COLORS[class].b)
  109.           return --no color change needed, bar is in class color
  110.         end
  111.       end
  112.       if color.g+color.b == 0 then -- hostile
  113.         self.healthbar:SetStatusBarColor(FACTION_BAR_COLORS[2].r,FACTION_BAR_COLORS[2].g,FACTION_BAR_COLORS[2].b)
  114.         return
  115.       elseif color.r+color.b == 0 then -- friendly npc
  116.         self.healthbar:SetStatusBarColor(FACTION_BAR_COLORS[6].r,FACTION_BAR_COLORS[6].g,FACTION_BAR_COLORS[6].b)
  117.         return
  118.       elseif color.r+color.g == 2 then -- neutral
  119.         self.healthbar:SetStatusBarColor(FACTION_BAR_COLORS[4].r,FACTION_BAR_COLORS[4].g,FACTION_BAR_COLORS[4].b)
  120.         return
  121.       elseif color.r+color.g == 0 then -- friendly player, we don't like 0,0,1 so we change it to a more likable color
  122.         self.healthbar:SetStatusBarColor(0/255, 100/255, 255/255)
  123.         return
  124.       else -- enemy player
  125.         --whatever is left
  126.         return
  127.       end
  128.     end
  129.  
  130.     --NamePlateInit func
  131.     local function NamePlateInit(plate)
  132.       --add some references
  133.       plate.barFrame, plate.nameFrame = plate:GetChildren()
  134.       plate.healthbar, plate.castbar = plate.barFrame:GetChildren()
  135.       plate.threat, plate.border, plate.highlight, plate.level, plate.boss, plate.raid, plate.dragon = plate.barFrame:GetRegions()
  136.       plate.name = plate.nameFrame:GetRegions()
  137.       plate.healthbar.texture = plate.healthbar:GetRegions()
  138.       plate.castbar.texture, plate.castbar.border, plate.castbar.shield, plate.castbar.icon, plate.castbar.name, plate.castbar.nameShadow = plate.castbar:GetRegions()
  139.       plate.rdp_checked = true
  140.       --create new plate and parent it to RDP
  141.       RDP.nameplates[plate] = CreateFrame("Frame", "New"..plate:GetName(), RDP)
  142.       RDP.nameplates[plate]:SetSize(cfg.width,cfg.height)
  143.       RDP.nameplates[plate].width, RDP.nameplates[plate].height = RDP.nameplates[plate]:GetSize()
  144.       --keep the frame reference for later
  145.       RDP.nameplates[plate].blizzPlate = plate
  146.       plate.newPlate = RDP.nameplates[plate]
  147.       --reparent
  148.       plate.raid:SetParent(RDP.nameplates[plate])
  149.       plate.nameFrame:SetParent(RDP.nameplates[plate])
  150.       plate.castbar:SetParent(RDP.nameplates[plate])
  151.       plate.healthbar:SetParent(RDP.nameplates[plate])
  152.       --reparent raid mark
  153.       plate.raid:ClearAllPoints()
  154.       plate.raid:SetPoint("BOTTOM",RDP.nameplates[plate],"TOP")
  155.       --hide level
  156.       plate.level:Hide()
  157.       --hide textures
  158.       plate.dragon:SetTexture(nil)
  159.       plate.border:SetTexture(nil)
  160.       plate.boss:SetTexture(nil)
  161.       plate.highlight:SetTexture(nil)
  162.       plate.castbar.border:SetTexture(nil)
  163.       plate.castbar.shield:SetTexture(nil)
  164.       plate.threat:SetTexture(nil)
  165.       --plate.castbar.nameShadow:SetTexture(nil)
  166.       --healthbar bg
  167.       plate.healthbar.bg = plate.healthbar:CreateTexture(nil,"BACKGROUND",nil,-8)
  168.       plate.healthbar.bg:SetTexture(0,0,0,1)
  169.       plate.healthbar.bg:SetAllPoints()
  170.       --name
  171.       plate.name:SetFont(STANDARD_TEXT_FONT,11,"THINOUTLINE")
  172.       plate.name:SetShadowColor(0,0,0,0)
  173.       --castbar icon
  174.       plate.castbar.icon:SetTexCoord(0.1,0.9,0.1,0.9)
  175.       plate.castbar.icon:SetSize(RDP.nameplates[plate].height,RDP.nameplates[plate].height)
  176.       --castbar bg
  177.       plate.castbar.bg = plate.castbar:CreateTexture(nil,"BACKGROUND",nil,-8)
  178.       plate.castbar.bg:SetTexture(0,0,0,1)
  179.       plate.castbar.bg:SetAllPoints()
  180.       --castbar spellname
  181.       plate.castbar.name:ClearAllPoints()
  182.       plate.castbar.name:SetPoint("BOTTOM",plate.castbar,0,-5)
  183.       plate.castbar.name:SetPoint("LEFT",plate.castbar,5,0)
  184.       plate.castbar.name:SetPoint("RIGHT",plate.castbar,-5,0)
  185.       plate.castbar.name:SetFont(STANDARD_TEXT_FONT,10,"THINOUTLINE")
  186.       plate.castbar.name:SetShadowColor(0,0,0,0)
  187.       --nameplate on show hook
  188.       plate:HookScript("OnShow", NamePlateOnShow)
  189.       --nameplate castbar on show hook
  190.       plate.castbar:HookScript("OnShow", NamePlateCastbarOnShow)
  191.       --i fix
  192.       NamePlateOnShow(plate)
  193.       --debug
  194.       --local t = plate:CreateTexture()
  195.       --t:SetTexture(1,0,0,0.3)
  196.       --t:SetAllPoints()
  197.     end
  198.  
  199.     --IsNamePlateFrame func
  200.     local function IsNamePlateFrame(obj)
  201.       local name = obj:GetName()
  202.       if name and name:find("NamePlate") then
  203.         return true
  204.       end
  205.       obj.rdp_checked = true
  206.       return false
  207.     end
  208.  
  209.     --SearchForNamePlates func
  210.     local function SearchForNamePlates(self)
  211.       local currentChildren = self:GetNumChildren()
  212.       if not currentChildren or currentChildren == 0 then return end
  213.       for _, obj in pairs({self:GetChildren()}) do
  214.         if not obj.rdp_checked and IsNamePlateFrame(obj) then
  215.           NamePlateInit(obj)
  216.         end
  217.       end
  218.     end
  219.  
  220.     --NamePlateOnUpdate func
  221.     local function NamePlateOnUpdate()
  222.       RDP:Hide()
  223.       for blizzPlate, newPlate in pairs(RDP.nameplates) do
  224.           newPlate:Hide()
  225.           if blizzPlate:IsShown() then
  226.             newPlate:SetPoint("CENTER", WorldFrame, "BOTTOMLEFT", blizzPlate:GetCenter())
  227.             newPlate:SetAlpha(blizzPlate:GetAlpha())
  228.             NamePlateNameUpdate(blizzPlate)
  229.             NamePlateHealthBarUpdate(blizzPlate)
  230.             newPlate:Show()
  231.           end
  232.       end
  233.       RDP:Show()
  234.     end
  235.  
  236.     --OnUpdate func
  237.     local function OnUpdate(self)
  238.       SearchForNamePlates(self)
  239.       NamePlateOnUpdate()
  240.     end
  241.  
  242.     -----------------------------------------
  243.     -- INIT
  244.     -----------------------------------------
  245.  
  246.     WorldFrame:HookScript("OnUpdate", OnUpdate)

SVN: http://code.google.com/p/rothui/sour...ePlateTest.lua

Maybe someone can check the code.

If you disable RDP:Show() at the bottom no nameplate will show up. This shows at all the reparenting and stuff has worked.

FPS-wise it works as intended.

I tried integrating the threat glow color into the healthbar color. This kind of works but I don't like it that much.
__________________
| Simple is beautiful.
| WoWI AddOns | GitHub | Zork (WoW)

"I wonder what the non-pathetic people are doing tonight?" - Rajesh Koothrappali (The Big Bang Theory)

Last edited by zork : 08-21-13 at 02:48 AM.
  Reply With Quote
08-21-13, 04:06 AM   #8
semlar
A Pyroguard Emberseer
 
semlar's Avatar
AddOn Author - Click to view addons
Join Date: Sep 2007
Posts: 1,060
message redacted

Last edited by semlar : 11-04-15 at 01:50 AM.
  Reply With Quote
08-21-13, 05:46 AM   #9
zork
A Pyroguard Emberseer
 
zork's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2008
Posts: 1,740
Hmm. I will recheck on that one. I tested critters and they looked ok.

What I'm probably going to add is a throttle for search/update function calls. Currently they run on every OnUpdate. But that is only needed for the reposition functionality.
__________________
| Simple is beautiful.
| WoWI AddOns | GitHub | Zork (WoW)

"I wonder what the non-pathetic people are doing tonight?" - Rajesh Koothrappali (The Big Bang Theory)
  Reply With Quote
08-21-13, 06:15 AM   #10
semlar
A Pyroguard Emberseer
 
semlar's Avatar
AddOn Author - Click to view addons
Join Date: Sep 2007
Posts: 1,060
Oh, you're right. I was actually using the scale of the bar group instead of the actual nameplate because I was experimenting with something.

Apparently the bar is scaled down, not the actual plate frame, so just disregard what I said.

On an unrelated note, I've been working on a way to show friendly nameplates but prevent you from interacting with them because I want to be able to see them without them getting in the way of what you're trying to click on.

Since you can't just :Hide() the nameplate in combat because of secure frame restrictions, I've had to get a little creative with SetCVar('nameplateShowFriends', 0) toggling them on to get the frame positions and immediately back off to prevent mouse interaction.

This actually works, but there is a 2 frame delay before plates appear after toggling them on which isn't a huge deal but it also causes the cursor to blink like crazy while it switches between states. I can probably counter that but I was wondering if anyone had any better ideas for how to do this.
  Reply With Quote
08-21-13, 02:03 PM   #11
zork
A Pyroguard Emberseer
 
zork's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2008
Posts: 1,740
Not sure semlar, sorry.

I'm nearly finished with my new nameplate addon.

Here is what I got so far:


*edit*

Tests are finished. rNamePlates2 is released.

http://www.wowinterface.com/download...mePlates2.html
__________________
| Simple is beautiful.
| WoWI AddOns | GitHub | Zork (WoW)

"I wonder what the non-pathetic people are doing tonight?" - Rajesh Koothrappali (The Big Bang Theory)

Last edited by zork : 08-21-13 at 06:01 PM.
  Reply With Quote

WoWInterface » Developer Discussions » General Authoring Discussion » Nameplate FPS drop research


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off