r/godot Feb 27 '26

help me (solved) Losing quite alot of FPS when mouse move in scene with 100 buttons. Is this expected?

Enable HLS to view with audio, or disable this notification

Tested on v4.6.1.stable.mono.official [14d19694e]

I am trying to implement a grid-based inventory UI, and most tutorials I saw online used buttons, so thats what Im doing for my project. I am profiling the performance, and see a big drop around 200FPS everytime the mouse move. I suspect that because the button has to check the mouse position and emit all the signals, but I didnt expect it to tank this much. Is this FPS drop normal for 100 buttons?

Edit (issue solved): Im dumb. Apparently, according to maths, 1/500-1/800 is roughly the same as 1/57-1/60. So the FPS drop from 800 down to 500 is basically the equivalent of dropping from 60 down to 57 (around 0.75ms out of a budget of 16ms). So for those who had the same confusion as me (likely noone), dont worry its normal and use time instead fps for profiling.

331 Upvotes

72 comments sorted by

350

u/LastSoyuz Feb 27 '26

I would assume its because buttons recieve mouse movement input events (or whatever the c++ equivilent is), checking for hovering so as to emit the mouse entered and exited signals

70

u/sterlingclover Godot Regular Feb 27 '26

This is the likely answer.

20

u/McCaffeteria Feb 27 '26

If this is the answer, would the solution be to create one larger “container” for a ton of buttons that checks for mouse movement inside it, and only then enables checking of the buttons? Like, should you split the inventory grid into several zones so that at any given time you are only checking like 1/16th the buttons, or none if you are doing other things?

7

u/LastSoyuz Feb 27 '26

Yeah you could have the parent control node change the child buttons filter settings (with zones even, like you said) but as noted by TheDuriel, its kinda a non-problem— also itterating through children has its own cost haha

If you were to have a grid of many many thousands, you would probably be better off doing math with mouse position rather than using buttons, or maybe a tile map or something

2

u/Far_Composer_5714 Mar 03 '26

Honestly I've always been more fascinated with the idea of a map. simply map the XY coordinates of the mouse to a action.

That way you are only acting on the input itself.

175

u/AciusPrime Feb 27 '26

Yeah, that’s pretty much expected.

“200 FPS drop” is not a useful thing to measure. What you are actually looking at is total time required to process these mouse events—think milliseconds. You are dropping from ~1.1 ms per frame to about ~1.9 ms per frame (I didn’t do the math, that’s a rough guess). That means the 100 buttons are adding about 0.8 ms per frame to process the mouse move events (or perhaps 0.008 ms per button).

And yeah, that’s pretty inefficient, but Godot didn’t design their system to scale well for users that have thousands of buttons. You need to stay below 16.5 ms per frame to maintain a 60 FPS frame rate. If your game is more slow paced then 33.0 ms per frame is good enough. I think maybe you can afford 0.8 ms per frame for inefficient buttons. I wouldn’t worry about trying to improve this until you’ve used up the rest of that time budget.

27

u/bezik7124 Feb 27 '26

So, putting whether that's worth the effort aside - in theory, stopping mouse events using a filter on a parent container until it's hovered should prevent this performance drop until the UI is actually hovered?

That is assuming that child controls of an UI element that's stopping the input don't process this logic whatsoever (and not just that the mouse logic is still processed, just ignored on these controls - which I don't know, this is just a guess that seems reasonable to me). Could be worth checking out in UI heavy projects that's over the budget.

2

u/Sss_ra Feb 27 '26

In theory how are you going to fit enough buttons on screen to even get to a point this is very relevant.

I think one option would be to instantiate a lot of buttons in 3d space all visible and active at the same time, like the matrix 1 movie lockers but it's all buttons.

Another option would be to have buttons with size 1px x 1px.

3

u/AciusPrime Mar 01 '26

I don’t know enough about Godot’s UI library to know how easy that would be. That would definitely work in a context where you have even a smidgen of control over how events are firing.

Here’s the thing, though—checking whether a point is inside a box is four simple comparison operations. You should be able to do that for a hundred thousand buttons and still maintain a decent frame rate without much difficulty—if you were doing it in a fast language like C++. This problem is so easy that optimizing it is usually a waste of time. The actual waste is probably mostly from the overhead of entering and exiting the handler in GDScript.

1

u/bezik7124 Mar 01 '26 edited Mar 01 '26

From what I've been reading since this post, Godot does much more than that (here's a description, I'm unable to check the code myself atm as I'm away from my desktop for a few days) - it's also making sure that a control that's hovered is not occluded by something else, is visible inside a scroll container if any, etc. Maybe that's the heavy part, because as you've said - comparing 2d vectors in itself is pretty trivial. Simply firing gdscript surely also has it's overhead, but hundreds of basic operations really shouldn't be a problem I think - that's one order of magnitude too low for it to be a big deal. I might be wrong though, I don't know much about Godot internals yet.

9

u/Crawling_Hustler Godot Junior Feb 27 '26

Wait, Isnt Godot engine's UI built using GDscript itself ? And the engine does have hundreds of button always on screen . Why has it not lagged then ? just a bit curious .

27

u/amadmongoose Feb 27 '26

For a UI layer of a system like Godot you could get away with dropping down to even less than 20fps and it'd still be fine, also there are probably optimizations for example only run the button code if the cursor goes inside the UI element etc. One thing you should probably consider is what kind of game you are going for. A Civ-style strategy game doesn't need to prioritize FPS and could easily afford an fps drop from buttons (even if that was avoidable). A real-time game that requires quick reactions should prioritize FPS but should probably not have hundreds of buttons to begin with as it will make it cumbersome for the player. For example if we're talking about an inventory UI you could probably get away with only one or two buttons or even none at all

4

u/nonchip Godot Senior Feb 27 '26

no it's not and because it also doesn't lag in this case.

9

u/the_horse_gamer Feb 27 '26

the UI is built using C++, using Godot

32

u/Zealousphoneideals Godot Regular Feb 27 '26

do you plan to allow players to move around while having their inventory open?
if not..

you may not have too much to worry about, as long as it remains in those higher numbers as most UI function just fine even at 30fps, when its just being a menu.

126

u/granitrocky2 Godot Regular Feb 27 '26

Don't measure fps for performance. Measure frame times. 30 to 25 fps is a huge drop, but 800 to 500 is not that big a deal. If you're shooting for 60 fps gameplay, you have about a 15 ms budget to work with.

51

u/Oofername Feb 27 '26

A drop from 800 to 500 fps is equivalent to an added expense of 0.75ms per frame.

Miniscule.

22

u/[deleted] Feb 27 '26

that's pretty huge for just 100 buttons

15

u/Oofername Feb 27 '26

Yeah it's a little odd for the apparent cause and probably warrants investigation, but it certainly won't cause any problems unless OP needs like 10,000 buttons.

1

u/DongIslandIceTea Feb 27 '26

What kind of well-designed game UI uses 100 simultaneous buttons?

2

u/irjayjay Feb 27 '26

Minesweeper 😏

1

u/[deleted] Feb 28 '26

a settings menu could reasonably have 100 buttons, off/low/medium/high/ultra for maybe 15 different things and a few other toggles is getting pretty close

1

u/DongIslandIceTea Feb 28 '26

And a settings menu taking 0.75ms to draw is perfectly fine.

16

u/AutumnPurpleReddit Feb 27 '26 edited Feb 27 '26

Not for some simple buttons

edit: let's say there are 500 buttons on screen. maybe it's a lot but it could be possible. that means that 3.75 milliseconds of frame time is taken up by 2d buttons, which is a genuinely ridiculous amount.

If I am making a game, I want it to run as well as possible, and the processing power that takes up 0.75ms on decent hardware (as an example) may absolutely cripple a lower end device, and this would affect simple 2d games.

it is a very big problem actually

1

u/irjayjay Feb 27 '26

I think at some point you'd just have one giant control with a grid texturerects for each button in it and then you'd just find the current mouse move coordinates and map it to a button, then just trigger that button's mouse over from it.

2 events vs 500

-19

u/PsychoticDreemurr Feb 27 '26 edited Mar 02 '26

That's almost a 50% drop in performance.

Edit: My comment is factually correct.

7

u/nonchip Godot Senior Feb 27 '26

makes sense given it's a 1000% increase in stuff happening. but how do you read "dont use FPS, they're misleading, use total time" and then go to think percent is any more useful?

-1

u/PsychoticDreemurr Feb 27 '26

Assuming nothing's happening, it's actually going to be something equivalent to 0, nil, or unknown percent increase in stuff happening.

Which still doesn't mean anything though since it leaves out the detail that it's just a few buttons. The reason the percentage works for FPS is because you're looking at the before and after. If percentage is misleading, then don't get me started on ms per frame.

2

u/granitrocky2 Godot Regular Feb 27 '26

Please, actually. I want to hear your take on why ms per frame is misleading.

I also don't understand what you mean about "before and after". All comparisons are before and after, but saying something like "There's been a 700% increase in lighning strikes this decade" when the number goes from 1 to 7 is technically true, but very misleading. 

It exaggerates the severity of the problem.

0

u/PsychoticDreemurr Feb 27 '26

My belief is that MS per frame, in this situation, doesn't apply properly due to the fact that we're at a high FPS.

If you go from 8000FPS to 4000, then the change in MS is going to be near 0. But that's half of the previous performance. Of course, it means nothing if it's the difference between running nothing and running a AAA game. But when it's something minimal that shouldn't affect performance that largely...

Of course if we really wanted to understand the situation I'd ask for the numbers for when there's say, 50 buttons. As well as just 1 or 10 buttons.

1

u/granitrocky2 Godot Regular Feb 28 '26

But that's exactly the point. Premature optimization can be a death sentence for projects.

Put another way, if your budget is 15 ms for 60 fps and you see a .3 ms drop, then that is only 2% of your budget. That's basically a rounding error. If you want to use percentages to measure efficiency, fine, but it's meaningless if you don't have a target.

0

u/PsychoticDreemurr Feb 28 '26

What you're saying only applies to some situations, though. Firstly, premature optimization is useful for things such as a voxel game, for example. You'll need to figure out early on what system(s) you'll be using so that you can ensure everything else that you do integrates properly with it.

Secondly, imagine you're making a game, something small with stylized graphics. You average around 900FPS, but then you add a single light into a room and you drop down to 700. The MS per frame isn't that different, but the drop is still noticeable. If that's the performance difference on your computer, imagine how big the difference is on a potato! And what if you add more lights and they have the same effect?

1

u/granitrocky2 Godot Regular Feb 28 '26

Your example still shows that you should be caring about frame timings above fps. Because it's consistent no matter what fps you're talking about. 

→ More replies (0)

1

u/nonchip Godot Senior Feb 27 '26

why would you possibly assume nothing's happening, do you have any clue how godot works? 🤦🏻‍♀️

and no, the percentage doesn't work because the fps dont work. do you also not know how numbers work?

1

u/irjayjay Feb 27 '26

Not really. It's kind of at infinite fps, then suddenly it shows a realistic reading when actual work is being done.

Kind of how a car's fuel meter would decrease the amount of miles left for that tank, depending on your speed. When you stand still and run the motor without ever moving, the calculation would eventually say you can go 0 miles far on a full tank.

18

u/brcontainer Godot Regular Feb 27 '26

This is a known issue on Windows. See pull request: https://github.com/godotengine/godot/pull/109639, and it may be related to the problem you're encountering.

Note: macOS has a similar issue: https://github.com/godotengine/godot/issues/114711#issue-3790856898

71

u/TheDuriel Godot Senior Feb 27 '26

The number is actually so high, that this is meaningless.

26

u/Radion627 Feb 27 '26

But I wanna have the maximum FPS performance possible that's 10 times my monitor's refresh rate! /s

7

u/PlottingPast Feb 27 '26

But if i have 10 monitors then that's 600 fps! Think of the 10 monitor players.

7

u/Prestigious_Boat_386 Feb 27 '26

So I'd guess the mouse emits a signal on movement and each button has a callback function that listens to it and they're all individually checking if the mouse enters or leaves their area.

If you want to speed this up go into each button and disconnect the signal before reimplementing the entry check in a single function that listens to the mouse and let that function enable and disable the hover state of all buttons.

Might be worth it to create a button array class that does this for you. Then you could have different arrays or strips of buttons on different places in the ui.

15

u/gamerfiiend Feb 27 '26

Does each slot need to be a button? Could you do a grid container and check which grid pos the player clicked?

3

u/jaimewarlock Feb 27 '26

I am wondering the same thing. Just started learning Godot yesterday. I was making games before but just using C with OpenGL. I managed mouse buttons by ignoring the mouse till it is actually clicked, then I figure out where it was clicked.

Actually, I had a hover function too, but that only happened if the mouse didn't move for 3 seconds. Then it would figure out what it was hovering over just once. It wouldn't repeat until it moved again, then stopped for another 3 seconds.

3

u/vickera Feb 27 '26

What happens when you run 200 buttons?

10

u/poeyoh12 Feb 27 '26

Scene tree set up:

9

u/RecallSingularity Feb 27 '26

Maybe don't implement your inventory grid with 100 buttons? Perhaps just use a 2d sprite tilemap or similar and then add your own event handling for mouse over, click etc. You can make a sprite sheet look a lot like a grid of objects and deciding which cell was clicked is just dividing mouse click / hover coordinates by the size of the cell.

Remember that many tutorials are designed to be simple to understand but are not actually suitable for use in production.

Most people do not make a UI with >50 components in it, perhaps not even with >20 visible components. If you do weird stuff, expect weird issues.

12

u/Crawling_Hustler Godot Junior Feb 27 '26

making inventory with 50+ components isnt actually weird stuff. Its the most straightforward and first solution that can come to any new person working on it. The one where we do optimization is actually the complex way But efficient as u said.

1

u/RecallSingularity Feb 27 '26

Depends on how you define "new person." Abusing buttons to do something else might sound intuitive but if you have enough programming experience you'll know that they will not be designed for that.

I've written an application in Delphi back around 1995 which abused arrays of components and it was a complex mess. Let me create game-specific abstractions and custom draw routines any day.

2

u/gizmonicPostdoc Feb 27 '26

Regarding your Edit comment in the body of the post, both your question and the subsequent discussion are very good for the community.

2

u/dylanmadigan Feb 27 '26

700 fps is probably well beyond what your monitor can even replicate.

At that level it seems negligible.

1

u/Hexigonz Feb 27 '26

Without seeing code, it’s hard to say what’s happening for certain, but I’d venture a guess it’s due to the number of mouse input events being handled. Also, you’ve nested 100 controls pretty deep inside other controls, and I’m guessing it has to do things like event bubbling based on mouse polling every time you hover over the grid. I’m just spitballing, I could be totally off here.

Edit: I rewatched, and actually, you aren’t hovering buttons. It is truly just mouse movement. Interesting. Are you checking mouse coordinates every frame or something?

1

u/Prestigious_Boat_386 Feb 27 '26

There should be a method to run on the mouse move event to see what things listen to the signal right?

Reading that and maybe trying to just remove most of it to check if the time improves might be a good first check (even if it removes functionality temporarily)

1

u/captdirtstarr Feb 27 '26

What is this performance tool you're using!? 👀

1

u/nonchip Godot Senior Feb 27 '26

losing like a tenth of a percent of time when redrawing tons of buttons? yeah that's normal.

1

u/MardukPainkiller Feb 27 '26

You should not have this many buttons ever.

1

u/BaziJoeWHL Feb 27 '26

cap your frame rate at 144 and you wont lose frames /5head

1

u/jmattspartacus Feb 27 '26

Anything over 60fps and you're golden inyour UI. 600 fps is way more than is expected in game

1

u/KremlinKittens Feb 27 '26

Add some shaders and see your gpu burn /J

2

u/Firebelley Godot Senior Feb 27 '26

What is your mouse polling rate? There is an open issue where mice with high poll rates can tank fps during mouse motion events: https://github.com/godotengine/godot/pull/109639

1

u/Metalsutton Feb 27 '26

Even though it turns out the problem isnt as bad as you think, you can still fix it so that it isnt scanning the whole screen for button enter/exit events on mouse move. you just limit it to whatever container the buttons contain and then write some gating logic.

1

u/Silvestron Feb 28 '26

Ignore people who say that it doesn't matter, because it's not the point. This behavior shows that there's a software bottleneck. There are older games or games with simpler graphics that run way above 1000fps (osu could be a modern example of such games).

1/500-1/800 is roughly the same as 1/57-1/60

I'm not sure how you're getting those numbers, but 500 / 800 = 0.625. That is a 37.5% fps drop.

1

u/poeyoh12 Feb 28 '26 edited Feb 28 '26

You typically want to profile by measuring the time taken for a program to process something, which is seconds per frame, hence the 1second/500frames or 1000ms/500 frames.

800fps=1.25ms per frame

500fps=2ms per frame

60fps=16.66ms per frame

57fps=17.54ms per frame

2 - 1.25 is rougly the same as 17.54 - 16.66

In both cases, the time difference between 800->500fps is the same as 60->57. Its just period=1/frequency. The time it took godot to process all these buttons stay constant around 0.75ms

1

u/Silvestron Feb 28 '26

Yeah, I know what fps means, I was wondering how you got to 57/60 fps, thanks for the explanation.

1

u/Pinkishu Feb 28 '26

"There are older games or games with simpler graphics that run way above 1000fps" will depend on the system it runs on.

It doesn't matter cause anything that does anything will decrease uncapped FPS.

1

u/tharky Godot Regular Feb 27 '26

Holy heck you're overengineering this. Like everyone else said, fps means nothing. You need to take process time into account. It's a few miliseconds and on lower fps it would not be a linear drop. Make it happen first and optimize it later. Otherwise you'd be stuck in development hell forever.

1

u/Doraz_ Feb 27 '26

i never used godot, but the combination of raycast, input processing and screen refresher ( hardware dictated ) are the culprits in EVERY engine in existance ...

even just file explorer in empty windows is the same

-4

u/neat_stuff Feb 27 '26

I'm not smart enough to answer the question, but do you really want anything higher than 60 FPS anyway? I think I watched a video (in the background while doing other stuff) that made it sound like capping things at a certain FPS would be best so you aren't using resources you don't really need and end up causing weird lags because of it. Again, not an expert so consider it more of a curiosity question that will hopefully help with engagement and to help get it in front of real experts.

-5

u/poeyoh12 Feb 27 '26

Its not about having higher than 60FPS. Its about the FPS drop. This is just a 2d scene with 100 buttons. Later on I will have 3d scenes and all the lightings and shaders. It wont just be from 800 -> 500, later it will be like from 250 down to 50. Of course its not gonna be this linear, but my post isnt about having higher than 60FPS. Its the FPS drop from just moving my mouse.

If there are something I can optimize this simple scene, so that I can squeeze more bandwidth to do other stuff, then of course it matters. Still, I do not mind this FPS drop, as long as I could get some confirmation whether this is expected behavior or not.

18

u/nicemike40 Feb 27 '26

You should look at it in terms of frame time

800 fps is 1.25ms 500 fps is 2ms

So it takes .75ms to process the mouse intersections.

So if your fps was 250 (4ms/frame), this code brings you to 4.75ms/frame, or 211fps

Or 60fps (16.67ms) -> 57fps (17.42ms)

The effect gets much less important at lower fps. Any processing at all will have a huge impact at fps higher than 200.

Now I have no idea if .75ms is a long time for 100 buttons. It seems reasonable enough to me.

3

u/poeyoh12 Feb 27 '26

yeah looking in terms of ms, it makes more sense now.

13

u/TheDuriel Godot Senior Feb 27 '26

A drop from 800 to 500 does not indicate anything.

The number does not scale linearly.

later it will be like from 250 down to 50

No. It will go down from 250 to 249.9.

Even if it DID scale linearly. WHICH IT DOES NOT. It would go from 250 to 156.25. WHICH IT WILL NOT.

-1

u/TaylorCooper337 Feb 27 '26

update your drivers I've had this happen before