@skar I have suspected there may be a timing issue with the viewer.prefferedFPS value the overlay sets for some time now as I have seen similar things on a 60Hz iPad Air. Even just running the empty overlay project I often see something similar and it fluctuates between 40 & 50 fps for some time; then I restart the project and it’s perfectly fine locked at 60 fps… :neutral: I’m not sure there’s much that can be done on the overlay side.
@skar Exactly! Where Codea totally ignores if you’ve even finished rendering and just displays however far it got. Would end up with some interesting bugs for sure! :lol:
Regardless if you set it to 120, if your game or app takes too long to render a frame then the device will step down to the next framerate it thinks you can maintain (i.e. 80 in this case). This might fluctuate as your render times change. It might not be possible to get an accurate reading of the true FPS inside codea either. I’ll look into an accurate way to measure the fps and add an option to retrieve it directly
Btw at 120 fps would mean you have less than 8.3ms to render your entire frame out. If it takes longer than that, it’s impossible to achieve that frame rate
From the official documentation:
You control a display link’s frame rate (the number of times the system calls the selector of its target, per second) by setting preferredFramesPerSecond. However, the actual frames per second may differ from the preferred value you set; actual frame rates are always a factor of the maximum refresh rate of the device. For example, if your device’s maximum refresh rate is 60 frames per second (defined by maximumFramesPerSecond), actual frame rates include 15, 20, 30, and 60 frames per second. If you set a display link’s preferred frame rate to a value higher than the maximum, the actual frame rate is the maximum.
In iOS 15, frame rate availability can change due to the system factoring in the system policy and user preference — including low power mode, critical thermal state, and accessibility settings.
The system rounds, to the nearest factor, preferred frame rates that aren’t a divisor of the maximum frame rate. For example, setting a preferred frame rate to either 26 or 35 frames per second on a device with a maximum refresh rate of 60 frames per second, yields an actual frame rate of 30 times per second.
Awesome, thanks for the info. So if it drops to 80 that’s a case of bottlenecks. I just wouldn’t expect any bottlenecks on these example games. However, I’m typically running something like YouTube in the background. I just did some tests with only Codea running and the examples were running at 120. So I probably stepped on my own feet here.
If you’re going to add a fps reader directly into Codea can you also have a way to report the device max fps? 120/60 max for me is all about the hardware, so if iPad Pro runs at 120 and the project dips to 80, I would expect on a 60hz it would drop to 30. So I would really only want to set the target to the device max and make sure I’m not dropping there. But I suppose it can be hard to maintain across all the different A12/13/14/15 variants out there without targeting the lowest device first.
I guess right now the only way to test would be to build on Xcode and test on a real device.
@skar Yes we can add the maximum as well (this is reported by the device anyway). It’s actually possible that you could get 60fps on an older device and still not reach 120fps on a newer one since your render budget is half on the newer device (~8.3ms vs ~16.6ms) but it may not be twice as fast (which is why you sometimes hit 80)
Most game developers will talk about CPU/GPU budget instead of raw frames per second. So if you measure how many milliseconds your frame costs to render (lets say you had 12ms instead of the target 8.3) then you can focus on reducing that number. We’re running on a scripting language interpreter, so immediately we have a CPU bottleneck, so we want to avoid too much processing per frame. In Lua virtually everything is a dictionary lookup (tables) which requires string hashing, etc. We can cache stuff in locals for fast access. We can also attempt to avoid creating a lot of garbage (caching vec2/vec3 objects instead of making new ones every time, etc…). In some cases I’ll just do v.x, v.y = v.x + 1, v.y + 1 instead of v = v + vec2(1, 1) since the later requires 2 additional allocations (temp vec2 and addition creates one as well)
Shade’s UI is written in Lua and a lot of our performance issues were related to things like matrices and vector math, as well as excessive garbage collection / allocations. Part of the problem is we decided to make a friendly and expressive API for quick and simple prototyping, so this means that we chose more expensive API calls (more allocations, like every time to add two vectors together it allocates a new one) to make it easier to write code. You’ll notice that ThreeJS in JavaScript has a lot of focus on reusing vectors and matrices, rather than making new ones
Multi-tasking and background processes will eat into your cpu/gpu budget as well. The original Codea runtime is pretty old now and relies on the Metal → OpenGL translation drivers that Apple wrote so its definitely not the most efficient engine out there…
Hmm yea a lot to consider, as performance chasing is not going to be as straight forward as I thought, but I’m not purely interested in performance, I could make the compromise of 60fps on Pro and 30fps on weaker devices.
I recall a while ago Simeon mentioned you are working on making Codea 4 closer to Metal. Will that mean it’s harder to use shaders?
@skar The new runtime has shader cross compilation using spirv-cross, so you can still write everything in GLSL. Some of the API is different and the 2D/3D parts (mesh, shader, model, material) have been unified. We’re using bgfx, sol3, entt in the background, which could potentially improve performance across the board. I haven’t done any benchmarks as of yet though