Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Touch input causes FPS drop and stuttering on some iOS devices (Pro Motion?) #76425

Open
djrain opened this issue Apr 25, 2023 · 42 comments
Open

Comments

@djrain
Copy link

djrain commented Apr 25, 2023

Godot version

3.6 beta 1

System information

iOS 16, GLES3

Issue description

Discussion started in #32139 but now seems like a separate issue.

I believe #69200 fixed that issue, but now I'm still seeing stutter and FPS drop from touch input on iPhone 14 Pro Max.
The issue is not reproducible on iPhone 12 or iPhone 6s, so I guess maybe this has to do with Pro Motion (high refresh rate).

Here are some screen recordings comparing a near-empty scene on iPhone 14 Pro Max and iPhone 6s. Godot icons indicate tapping input.

iPhone 6s - performance unaffected by touch input, as it should be:

6s.MP4


iPhone 14 Pro Max (high refresh rate DISABLED in Godot) - notice severe FPS drop:

14.disabled.mov


iPhone 14 Pro Max (high refresh rate enabled in Godot) - again, significant FPS drop:

14.enabled.mov

iPhone 14 is of course many times more powerful than the 6s, so this makes no sense at all. Especially the case where pro motion is not even enabled in Godot and the 14 is struggling.

Our game requires a lot of tapping, and these stutters are unfortunately making the difference between a nice smooth game, and a non-shippable one :( so we'd be super grateful for any help on this.

Steps to reproduce

Export and run MRP main scene on a recent iOS device (may require Pro Motion)

Minimal reproduction project

iOSTouchStutter.zip

@akien-mga
Copy link
Member

I wonder if this would help: #76399

There's a 3.x variant of the PR you could test.

That's just a hunch but maybe with the high refresh rate you're getting too many inputs processed and running potentially expensive logic for each event?

@djrain
Copy link
Author

djrain commented Apr 25, 2023

@akien-mga Could be worth a shot, thanks - weird thing is that the issue persists even if I disable high refresh rate in the phone settings. So perhaps it's not directly related...

@tbveralrud
Copy link
Contributor

tbveralrud commented Apr 26, 2023

That's just a hunch but maybe with the high refresh rate you're getting too many inputs processed and running potentially expensive logic for each event?

I believe the attached project doesn't have any logic run on input events yet still causes a stutter.

I looked at the Pro Motion support in the SDK. It allows for variable refresh rates to be set (see preferredFrameRateRange). But in practice, preferredFramesPerSecond will set the minimum, maximum, and preferred FPS to the value given.

Have you run Instruments with the Animation Hitches tool? It captures Core Animation data and gives insight into the CADisplayLink timing.

@djrain
Copy link
Author

djrain commented Apr 26, 2023

@tbveralrud I tried several runs, and it doesn't show any hitches.

Screen Shot 2023-04-26 at 11 09 33 AM

@djrain
Copy link
Author

djrain commented Apr 28, 2023

I wonder if this would help: #76399

Tried this, did not help unfortunately.

Also, I've tested on an iPhone 14 Plus (doesn't have ProMotion) and confirmed that the stutter doesn't happen there. So the problem must be related to ProMotion, or some other difference between 14 Pro Max and the 14 Plus (which there aren't many - only other big things are the dynamic island, and always-on display).

@oeleo1
Copy link

oeleo1 commented Apr 29, 2023

Hi,

We have a similar obscurity with 3.6beta1 on recent iOS devices only. The abnormal behavior doesn't happen on Android, Windows or when using 3.5.2. Only on recent iOS hardware.

What we see is that our player character is being triggered to jump, but it jumps 1/3rd of the time. The touch logic is in a touch controller which arms a jump_trigger and the player logic basically sets a variable jump in _process() to true based on the trigger, then _physics_process() reads that variable to alter the velocity. It's a classic:

var jump : bool = false

func _process(delta):
    ...
    jump = jump_trigger
    ...

func _physics_process(delta):
    ...
    if jump:
        velocity.y -= sqrt(JUMP_HEIGHT * 2*GRAVITY)
    velocity = move_and_slide(...)
    jump = false

What we see on the high-end iOS devices is that the jump variable is read as false in _physics_process() despite being set to true in _process(). This is very odd and happens sporadically.

It basically shows that either:

  1. _process() is executed more often than _physics_process()
  2. there is a memory issue.

In the 1. case, the axiom of having at most 1 idle frame per physics frame is broken (we are at the default 60fps without bumping refresh rates). This means that _process() is executed more often than _physics_process(). Could this ever happen? In any case, this works as intended in 3.5.2 on those devices, so it is really 3.6beta1 specific. Or is someone triggering internally a _process() call in 3.6beta1 under circumstances?

In the 2. case, all bets are off.

Unfortunately, we couldn't reproduce this with a stripped down version of an MRP so 2. is also a possibility.

PS: Some more debugging reveals that 1. is the cause of the problem: The logs show:

jump == false in _physics_process()
jump == false in _physics_process()
[1] jump = true after jump_trigger in _process()
[0] jump == true BEFORE jump_trigger in _process()
jump == false in _physics_process()
jump == false in _physics_process()
jump == false in _physics_process()

In our case, jump is being written twice, at first to true then to false, without _physics_process() noticing the change.

So it boils down to why _process() is being executed more often than _physics_process() or, in other words, how come the physics frame rate is being slowed down below the idle frame rate.

@Calinou
Copy link
Member

Calinou commented Apr 29, 2023

Can anyone reproduce this on 4.0.2?

@djrain
Copy link
Author

djrain commented Apr 29, 2023

After doing a bit more digging, this may just be an issue on Apple's side. There are a number of reports about iPhone 14 Pro stuttering, some specifically regarding touch:
https://forums.macrumors.com/threads/why-isnt-apple-fixing-the-touch-input-stutter-on-iphone-14-pro-models.2370587/
https://developer.apple.com/forums/thread/718721
https://www.reddit.com/r/iOSProgramming/comments/10gatwu/iphone_14_pro_stutter_when_tapping_display/
https://www.reddit.com/r/iphone/comments/yyv06x/why_isnt_apple_fixing_the_touch_input_stutter_on/

And I just noticed that I can see the same kind of stuttering in some other games, for instance Tiny Wings.

It sounds like Apple has been aware of this for some time, but it still hasn't been fixed properly. I just updated my phone to latest iOS (16.4.1) and it did not help.

@oeleo1
Copy link

oeleo1 commented Apr 29, 2023

@djrain It sounds tempting to assume that the problem is on Apple’s side, but I see no rational explanation on why the problem is not present on previous Godot versions on the same hardware. BTW the touch logic works fine for us on both 3.5.2 and 3.6beta1. So for me this remains a major obscurity. Maybe a bisect on potential commits which may have broken it would be in order…

@djrain
Copy link
Author

djrain commented Apr 29, 2023

@oeleo1 I'm not sure, but it sounds to me like you may have a different problem there. It could be related, but I would suggest opening a new issue to look into that, as it does sound concerning!

@Calinou
Copy link
Member

Calinou commented Apr 29, 2023

@djrain Can you reproduce this issue on 3.5.2 or older?

@djrain
Copy link
Author

djrain commented Apr 30, 2023

@Calinou yes, I've reproduced this in 3.5.1 and 4.0.2 RC2.

@tbveralrud
Copy link
Contributor

@oeleo1 I agree that a new topic is appropriate for your issue.

In the meantime, jump = jump or jump_trigger should unblock your situation.

@oeleo1
Copy link

oeleo1 commented May 1, 2023

Yes, I need to spawn another ticket.

@tbveralrud Yes, we already tried that jump = jump or jump_trigger idea but it shifts the same problem to double taps. Better not go down this road at this point unless we have some more visibility on what's going on here. In the meantime we're happy with 3.5.2.

@k0rean-rand0m
Copy link

k0rean-rand0m commented Oct 3, 2023

Godot v4.1.1
Same issue on iPhone 14. iPhone 12 runs the game smoothly. Tested via TestFlight, so it's the "production" build. Turning on 60 hertz makes the frame drops a little less visible, but they still appear. The frame drops appear when processing input (tested with custom in-game FPS profiler)

@MarcusDobler
Copy link

Godot 4.2 same issue. Is there any chance this will get fixed? Very bad user experience on iOS.

@oeleo1
Copy link

oeleo1 commented Jan 16, 2024

I am not positive the problem is identified or understood in order to propose a fix. Maybe we shall provide @lawnjelly with an iPad Pro (all iPad Pros from Gen 1 have ProMotion) so he can at least see the problem. Then again, not sure Godot can do something better than the logic in place with the frame rates (detection + adjustment) to remedy the situation.

@oeleo1
Copy link

oeleo1 commented Jan 28, 2024

PS: I am very happy to report that the iPad Pro / iPhone stutter (presumably due to ProMotion rendering rate variations and input lags as reported here) are gone when we switched to FTI for 2D (Fixed Timestamp Interpolation) with the release of Godot 3.6beta4 - the 1st and long awaited 3.x release supporting FTI for 2D. Stutter completely gone, as FTI deals with it perfectly and the game runs smoothly as it run on other devices without ProMotion. So I encourage everyone here to retest their projects with FTI enabled.

For us switching to FTI was a no brainer. Just enabling the global setting, renaming a couple of _process() functions and logic to _physics_process() and a few node.reset_physics_interplation() calls here and there, essentially at places where we have set_global_position() calls for some objects or (CPU) Particles with local coordinates set to off.

Excellent work @lawnjelly and Team! Thank you very much! For us this 3.6beta4 release is a huge milestone in both quality and functionality!

@djrain
Copy link
Author

djrain commented Jan 29, 2024

@oeleo1 Are you sure the interpolation didn't solve a different issue in your game? The MRP here still reproduces the touch stutter for me in 3.6 beta 4 with physics interpolation enabled. The project doesn't use any physics, so I don't see how that setting would affect it.

@lawnjelly
Copy link
Member

The project doesn't use any physics, so I don't see how that setting would affect it.

The name "physics interpolation" is misleading, but Juan insisted, as it is a simpler term for beginners. It is usually known as "fixed timestep interpolation". It has nothing to do with physics (except in this case the fixed timesteps coincide with physics steps in Godot).

That said there is likely more than one issue being reported here. Some problems may be due to input threading (which has some fixes already in 3.6) and some problems may be due to lack of FTI. There may also be additional factors.

@oeleo1
Copy link

oeleo1 commented Jan 29, 2024

@djrain I am quite confident the stutter we had was related to the varying 115-120 fps ProMotion devices with 60 tps physics.

The MRP here still reproduces the touch stutter for me in 3.6 beta 4 with physics interpolation enabled. The project doesn't use any physics, so I don't see how that setting would affect it.

Are you sure you still have stutter ? Or are you referring to the FPS drops due to input which do not necessarily result in stutter ? These two are different issues. With FTI, the FPS variations are still there, but the stutter is gone. That's what I am reporting.

Now, on the iOS input resulting in FPS drops, two things are worth mentioning here:

  1. The iOS specific input code has been removed recently and unified with the rest of the supported devices. no more specific buffering or iOS specific delays which we had in previous versions of Godot and we could configure in settings. So Godot can't du much more than that codewise at this point, but we could indeed analyze the problem further on iOS devices on why it happens.
  2. Your MRP exhibits the lot's of work in the input handler pattern @akien-mga mentioned earlier. On every key press, you are loading dynamically a scene from the file system, which in turn is instanced dynamically to create objects, which in your case happens to be an AnimationPlayer with properties set to di a fadeout. That's quite a lot of work for an input processor an you definitely might want to optimize that in order to reduce any potential lags here. For instance, you could a) preload your scene, b) instance it outside the input handler, c) just clone the instance in the input handler and use the clone, which is a thousand times faster than instancing the scene after loading it dynamically. Etc.
  3. Generally speaking of input processing in Godot, it is worth mentioning a few optimizations one may consider for real-time games
  • Put you touch input logic at the bottom of the tree. This is because Godot processes its input events bottom-up and you want your input event to be processed as early as possible, and not after walking through thousands of nodes.
  • Put an input processing "pit stop" node above your touch controller, which catches all input and stops its propagation upwards in the tree, thus preventing the event to walk through all the nodes in your game. Your mileage may differ, but you gate the idea. You can achieve the same result by putting you main non-input related part of the game in viewport(s) with input disabled.
  • Make sure Input.set_input_as_handled() is used redularily when you catch an event of interest that you are processing.

All this to say that the MRP here is causing delays for sure, but I am not positive it is causing and exhibiting stutter.

@oeleo1
Copy link

oeleo1 commented Jan 29, 2024

Just to clarify. In order to observe stutter, there has to be a moving object. The MRP doesn't have one. Just fading sprites appearing at the rate of the screen taps.

The MRP and this bug report is about an FPS lag which is directly related to the input processing on iOS devices with ProMotion. The Fixed Timestamp Interpolation enabled in settings definitely solves most, if not ALL of the stutter one may have on a variable frame rate rendering device. So with FTI there is no stutter issue. There is an input processing issue which I have tried to break down above with some practical tips on reducing the phenomenon.

What is still puzzling here is that on iOS devices without ProMotion, there are no input lags, while there ae lag on device with ProMotion - characterized with a (potentially variable, as per spec) rendering rate of 120 fps. But given that Godot's input code is standardized across devices and the observed phenomenon seems specific to Apple devices with ProMotion, Godot's due diligence homework is about figuring out why ProMotion results in screen touch input lags. This may be an Apple-specific problem, or a Godot threading priority problem showing up in this specific scenario.

Hope this helps explaining where we stand on this topic.

@djrain
Copy link
Author

djrain commented Jan 29, 2024

@oeleo1 okay, so in the MRP I added some sprites moving across the screen and removed all logic on input. And after enabling the interpolation, I'm still seeing plenty of visual stuttering on touch (in addition to the frame drops). I'm moving the sprites in _physics_process(). Is there another step I'm missing? I guess I don't understand what the new setting does exactly.

@oeleo1
Copy link

oeleo1 commented Jan 29, 2024

Is there another step I'm missing?

Who knows :-) Can't say without looking at the new MRP. Moving the sprites is a vague notion here. Usually moving means tweening the position or using the move_and_slide() family of functions within _physics_process(). Setting the position explicitly, be it in _process() or _physics_process() is a no go. Do you have a Camera2D node? You may want to add one in order to have explicit control on the visuals and the target to follow.

I guess I don't understand what the new setting does exactly.

The new setting smoothes movements for you automatically by using the so called Engine.get_physics_interpolated_fraction() without you having to worry when and how to use it. The net effect is that any rendering occurring between 2 physics ticks is interpolated automatically for you. Before FTI for 2D, to achieve the same effect, one had to lerp() positions manually and maintain transform state variables between _process() and _physics_process() in lockstep. This is now done automatically for you by ticking on the new setting.

ProMotion typically uses adaptive rate rendering "up to 120 fps" while your Godot physics ticks are nominally fixed at 60 tps. So in theory, you shall have something like X-2.0 rendered rames per physics tick, where X varies typically from 1.5 to 1.9 with ProMotion. In short, when a frame is rendered on the screen, FTI makes sure the moving objects positions are computed properly so they appear exactly where they should be at the time the frame is rendered (which is a variable instant in time, happening before or after the physics tick). With FTI and a frame refresh rate of 110-120 fps with ProMotion, you shouldn't see a blink but a bunch of very smoothly moving objects.

@djrain
Copy link
Author

djrain commented Jan 29, 2024

Thanks for the info! I'm just still confused about how I should be moving stuff, since you said that setting position directly won't work? For example in my game, I don't use physics nodes. My "Player" entity is basically just a Node2D with a Sprite2D child. To move the whole player, I directly set the position of the Node2D in _physics_process(). Evidently this does not do the trick... So what would I do differently to let the interpolation magic happen?

@oeleo1
Copy link

oeleo1 commented Jan 29, 2024

So what would I do differently to let the interpolation magic happen?

Like I said, one of the simplest things you could do is to tween your player from pos_A to pos_B with a tween duration corresponding to your desired speed of movement. The granularity of the position and duration deltas is up to you. One usually uses velocity with move_and_slide() in the context of a KinematicBody2D. Not sure why you are not using it since it comes with plenty of goodies about collisions, frictions and the like.

@djrain
Copy link
Author

djrain commented Jan 29, 2024

Well, even using a KinematicBody2D and move_and_slide in _physics_process(), I'm not getting smooth movement.

Here's a video from my iPhone 14 Pro Max. The first cycle is smooth without touch input, and on the second cycle I start tapping and get a significant FPS drop and visual stutters:

RPReplay_Final1706564725.mov

@oeleo1 Would you mind looking at the project and showing me what I'm doing wrong? That would be a huge help!

iOSTouchStutter2.zip

@normano
Copy link

normano commented Jan 31, 2024

Application -> Run -> Delta Smoothing can be disabled for mobile only if that is a problem for mobile.

@oeleo1
Copy link

oeleo1 commented Jan 31, 2024

Messed up my multiple GitHub logins and identities, so repeating my post with the Godot version of myself ;-) Sorry about that.

@djrain Finally got a minute to look into your project. With a few project settings adjustments, it's smooth like a Greek olive sliding on a French butter toast :-) No stutter for me.

  • The general rule of thumb here is to enable Physics -> Common -> Physics Interpolation
  • The no less important bit is to enable Display -> Window -> iOS -> Allow High Refresh Rate (ProMotion 120 fps)
  • The crucial setting here is to disable Applicatin -> Run -> Delta Smoothing. This one is not your friend when you have a variable refresh rate like ProMotion. Not sure why it is enabled by default. This one constantly tries to compensate for variable refresh rate deviations and although I undesrtand the intent and the underlying logic, I disagree it shall be enabled by default... That's my opinion though and @lawnjelly & co. may disagree with it. I think it effectively does more harm than good, especially in the case of ProMotion.

Another piece of advice against stutter is to disable stdout and stderr :-). Although this doesn't apply to your project, any print output triggers useless complex formatting logic so if printing stuff is not absolutely necessary for debugging, stdout and stderr shall be off.

Your project with these remarks applied doesn't exhibit any stutter for me.
iOSTouchStutter2_fix.zip

@Calinou
Copy link
Member

Calinou commented Jan 31, 2024

The crucial setting here is to disable Applicatin -> Run -> Delta Smoothing. This one is not your friend when you have a variable refresh rate like ProMotion.

I wonder if there's a reliable way to detect VRR on all platforms, but last time I checked, it seemed difficult to impossible. That said, is ProMotion "true" VRR with a variable range between say, 48 and 120 Hz, or is it just a switch between 60 Hz and 120 Hz?

The way delta smoothing works reminds me of DuckStation's Sync to Host Refresh Rate feature, which is also recommended to be disabled on VRR displays:

image

@lawnjelly
Copy link
Member

lawnjelly commented Jan 31, 2024

The delta smoothing should turn off automatically with VRR (it's not intended to work in that scenario). If it doesn't that could be a bug, so worth reporting so I can investigate.

If you can compile from source, you can enable this define in main_timer_sync.h, which will report on whether it has locked, turn on / off etc:

//#define GODOT_DEBUG_DELTA_SMOOTHER

@oeleo1
Copy link

oeleo1 commented Jan 31, 2024

is ProMotion "true" VRR with a variable range between say, 48 and 120 Hz, or is it just a switch between 60 Hz and 120 Hz?

I guess that's an Apple secret :-) In practice, it looks like a switch 60/120. We observe whatever values are reported every second by Engine.get_frames_per_second(). When loading heavy scenes the value drops down to 105 fps then goes up to 120 within 2 seconds or so. That 1 sec interval is not very friendly to see exactly how variable the VRR is, but in reality it sticks to 120 fps and stays constant unless there are CPU/GPU load spikes.

@Calinou
Copy link
Member

Calinou commented Jan 31, 2024

That 1 sec interval is not very friendly to see exactly how variable the VRR is

You can measure the exact time taken between rendering two frames by subtracting Engine.get_ticks_usec() with the value from the previous frame (stored at the end of _process() in a member variable).

@djrain
Copy link
Author

djrain commented Jan 31, 2024

@oeleo1 So I opened your new project and exported it without any changes. Unfortunately, I'm still getting obvious stutters. Maybe I'm just tapping with more fingers or more rapidly than you are in order to see them?

Here is how that looks. On the 3rd pass of the icons, you can see the stutters after I tapped with 3 fingers, about 3 seconds in:

ProjectFixedNoChanges.mov

Potentially related issue is why the FPS is fixed at 60 while idle, and it jumps up to 120 temporarily after the touches?? I found that if I disable "hide home indicator", then the behavior is sort of opposite - it idles at 120, and drops a bit when tapped. Also, for some reason it seems I can only access the 120 FPS if the phone is screen recording, like I was here. If not recording, it only ever goes up to 80 fps... And all that only applies in Godot 3, I can't reproduce this odd FPS limit when not tapping in 4.2.1, there it always seems to run at a base of 120 as expected (though it still drops and stutters on taps).

All this to say, the fix isn't working for me, and it seems something is wrong with the high refresh rate in Godot 3 compared to 4.

@oeleo1
Copy link

oeleo1 commented Feb 1, 2024

@djrain To be honest, your latest stutter video looks much better to me than the previous ones, so I believe the new settings actually do work in your favor.

That said, your report sounds like a big mess of uncorrelated events. I believe you because real life is a mess of such events. :-)

Now, the only reasonable explanation is that currently Godot doesn't honor the VRR goodies dedicated to ProMotion for iPad/iPhone Pro models, nor does it support Adaptive-Sync displays on Macs. Lack of expertise, lack of time, lack of ressources, whatever - we just don't have it at this time.

What Godot actually does and excells at is that it is reasoning about frame rates based on the effective occurrence of the frames as time goes. The delta smoothing option is a statistical rate observer of past timings aiming at correcting deviations for a fixed frame rendering scenario (so again, no good for VRR). The FTI option takes the approach of taking the fixed rate physics ticks as a basis, then compensate the variable rendering by interpolating positions (which is much better for VRR). Etc. But all these techniques suffer from the lack of real hardware hints on what the actual rendering rate is or how and when it changes.

That's a problem indeed but I am sure that if you partner with @lawnjelly and the Godot iOS team you can put all the bits together in one place. So heads up on this!

FYI, Apple has actually published some very useful technical bits related to ProMotion. Please check this developer article and especially this excellent video about optimizing for VRR, Adaptive-Sync and ProMotion. There are enough technical details on how to detect the presence of VRR, how to configure the CADisplayLink with the goodies available and also best practices for securing a smooth custom rendering loop. That's spot on for what we are discusiing here.

That being said, I had a quick look at the current 3.x iphone code, and I didn't quite like the following bit in godot_view.mm:

- (void)drawView {
...
	if (self.useCADisplayLink) {
		// Pause the CADisplayLink to avoid recursion
		[self.displayLink setPaused:YES];

		// Process all input events
		while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
			;

		// We are good to go, resume the CADisplayLink
		[self.displayLink setPaused:NO];
	}
...

Why is there a while loop about input events in the middle of a drawView call ? Unclear... And this bit precisely is probably causing some of the stutter we see.

Also, the app_delegate.mm defines the godotView.useCADisplayLink flag in terms of some project setting which was unknown to me this far (and doesn't exist in the user menus):

viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;

All this to say that 1) we already benefit from the best settings at the time of this writing, 2) there is pending work to do in order to honor VRR for MacOS and iOS properly, 3) the above code probably neads a new look and a thorough review from the iOS folks.

Last but not least, we shall gift Apple Pro hardware to all of our Godot developer friends. Not sure how I can buy @lawnjelly an iPhone or an iPad Pro but if there are practical ways to do it, I am all ears and I'll do it :-)

@oxygen
Copy link

oxygen commented Apr 24, 2024

While I haven't looked at Godot's source code yet here are some things which need to be considered based on my empirical knowledge (I've started learning Godot less than a month ago, so maybe I'm super wrong).

My game needs requires input fine touch resolution (like in fine scrolling smth on very short lengths) for a good experience.

The screen itself influences how many touch events can be generated.

iOS
On an iPhone XS Max with a no name aftermarket screen (not Apple's) or a cheap Android phone: not enough touch move are sent (too much time between two touch move events).

Android
On the cheap Android phone things are even worse: a drop in FPS caused by anything (not touch) drops the number of touch events too.

As such, not enough touch events (for my case) because of a drop in FPS is something real to complain about and should be a bug as it would cause the user to compensate making things worse because the touch move events appear to first accumulate in a 10 events long buffer which is only sent to code in Godot after it fills - the worser the screen OR the worser the FPS then the more the user will overcompensate a touch move by moving the finger more. In extreme cases (lower than 20 FPS) this results in the user making a mistake because the 10 events buffer is eventually sent (with a big lag) to code (only touch move events

Conclusion
That is, it would be great if touch events had max priority in Godot and would be completely decoupled from how many frames of any kind are rendered, and would not be accumulated in buffers but sent to code as fast as possible without any lag induced in any way (such as waiting for a frame). This is the opposite of what the OP suggests.

So, based on what I've read above and the coupling of touch move events to screen quality AND the Godot's coupling to the
rendered rendered frames I'd say the OP is going down a vicious cycle (I didn't look at his source code):

The OP may be causing AND accelerating the FPS drop by spiking the CPU on every frame (on every touch event) resulting in a vicious cycle which eventually results in hitting the max processing capacity of the CPU and thus stuttering. If that's so then he can easily fix his issue (ProMotion displays come with powerful Apple CPUs, don't they?).

Maybe an empty project logging FPS, counting touch move events and profiling CPU usage on a jailbroken device would be needed to prove the OP's case if indeed Godot is taking too much CPU). The CPU profiler in the debugger is useless in his case: when working with iOS I have to detach the debugger in XCode because it is wasting so much CPU (depending on how much CPU processing my game makes) that it causes some SIGUSR1 signal which pauses the app (killing xcode resumes the game, couldn't figure out how to resume otherwise as the game would be killed).

Feel free to diss me.

@oxygen
Copy link

oxygen commented Apr 24, 2024

*only touch move events are buffered (which sucks), other events are sent in immediately (which is good).

@oxygen
Copy link

oxygen commented Apr 24, 2024

*I'm not sure if there is a 10 events touch move buffer on iOS, but if one exists then sending all those events at once would cause the CPU spike depending on what's processed on every frame (I guess one event would be sent for every consecutive frame).

This should be considered a bug in Godot (the buffer should be eliminated if the user isn't listening for gestures - then, a larger refactor should eliminate the coupling of touch events to rendered frames if such a coupling exists).

@Calinou
Copy link
Member

Calinou commented Apr 26, 2024

That is, it would be great if touch events had max priority in Godot and would be completely decoupled from how many frames of any kind are rendered, and would not be accumulated in buffers but sent to code as fast as possible without any lag induced in any way (such as waiting for a frame). This is the opposite of what the OP suggests.

Setting Input.use_accumulated_input = false in an autoload's _ready() method will disable input accumulation, so you can receive multiple input events for mouse/touch movement in the same rendered frame. This comes at the cost of increased CPU utilization, particularly if you have slow _input() methods in scripts. Therefore, input accumulation is enabled by default (it's a good idea to disable it for things like drawing apps).

@oxygen
Copy link

oxygen commented Apr 27, 2024

@Calinou Thank you, you saved me a lot of headache as I needed to measure the time passed between touch move events for some calculation. Also, I am now certain there is a touch move buffer on Android and no such buffer on iOS/Windows/Mac so it is an OS level Android issue.

@Calinou
Copy link
Member

Calinou commented Apr 27, 2024

Also, I am now certain there is a touch move buffer on Android and no such buffer on iOS/Windows/Mac so it is an OS level Android issue.

Agile input event flushing might help (it's only implemented on Android). It's disabled by default -- check the advanced Project Settings.

@oxygen
Copy link

oxygen commented Apr 27, 2024

@Calinou Thanks, its enabled already so that's not it. I've found lots of posts about this Android issue (feature), some call it "touch slop". I've found these docs, I'm still reading about it:

https://developer.android.com/develop/ui/views/touch-and-input/gestures/viewgroup#vc

""Touch slop" refers to the distance in pixels a user's touch can wander before the gesture is interpreted as scrolling. Touch slop is typically used to prevent accidental scrolling when the user is performing another touch operation, such as touching on-screen elements."

If this could be configured out of Godot it would make Godot more consistent accros platforms (and problably properly regulate the rate of touch events on Android).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants