Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Framerate Instability

edited October 15 in Bugs Posts: 256

Hi all,

I’ve noticed recently an annoying framerate instability causing stutters in animations. Even when doing absolutely nothing in the draw() function other than tracking framerate I’m seeing framerate drop to as low as 55 52 46 fps on a 60Hz device on occasion. That’s 14 frames dropped! :(

In most circumstances the framerate will sit happily at 60 fps throughout, but I’ve noticed the drops are more common immediately after launching a project from the editor and continue for the duration of the run. If I then re-run the project a few times the stutters eventually go away.

This is a slimmed down example of the issue and it will error as soon as the framerate dips below 60 fps:

FPS = 0
FPS_TARGET = 60

-- FPS Tracker
local frames = 0
local time = 0
function onFrame()
    time = time + DeltaTime
    if time > 1.0 then
        FPS = frames
        frames = 0
        time = time - 1.0

        -- Error if we're not at the expected FPS
        assert(FPS >= FPS_TARGET, string.format("Unstable framerate!\nFPS = %d\nFPS_TARGET = %d", FPS, FPS_TARGET))
    end
    frames = frames + 1
end

function setup()
    viewer.preferredFPS = FPS_TARGET
    parameter.watch("FPS")
end

function draw()
    -- No drawing, just FPS tracking
    onFrame()
end

If anyone could run this quickly and let me know if they’re seeing something similar that would be most appreciated.

Cheers,
Steppers

Tagged:

Comments

  • Posts: 2,496

    @steppers tried and crashed almost immediately several times. Attached image on last one.

  • edited October 15 Posts: 2,496
    @steppers - what OS are you running and what machine ? Mine is 10.9 iPad Pro with iPadOS 15.

    Since this OS added noted a some features are slower in general.
  • Posts: 256
    Yeah, the crash was the assert triggering because it wasn't at 60fps. I'm on an iPad Air 4, iPadOS 15 as well.
  • Posts: 188

    honestly might be because you’re using parameter.watch, it’s not a free operation and will run every frame

  • Posts: 256

    @skar It’s not :/ No watches here…

    fwiw, most of the time this runs with no issues at 60 fps. And another project does exactly the same thing and that is doing A LOT more…

    I’ve attached the project too. It tracks past frame times to determine the true framerate over the previous second.

  • dave1707dave1707 Mod
    Posts: 9,730

    Here’s another display of frame rate. The white line is the 60 fps base line. The red line is the current fps. Every now and then you’ll see a red dot above or below the base line which shows a change. I also display the max, current, and min fps. When the count starts to reach higher values, the current fps will start to run lower because of the display demand. But it pretty much stays the same early in the display with few deviations and kind of levels off as it slows down.

    viewer.mode=FULLSCREEN
    
    function setup()
        tab={}
        cnt=0
        curr,fmin,fmax=0,999,0
        smooth()
    end
    
    function draw()
        background(0)
        cnt=cnt+1
        stroke(255)
        strokeWidth(2)
        line(0,HEIGHT-100,WIDTH,HEIGHT-100)
    
        fps=1/DeltaTime
        stroke(255,0,0)
        fill(255,0,0)
        table.insert(tab,fps)
        for a,b in pairs(tab) do
            ellipse(a//20,HEIGHT-100-(60-b)*10,3)
        end
        fill(255)
        if fps<fmin then
            fmin=fps
        end
        if fps>fmax then 
            fmax=fps
        end
        text("tap screen to reset",WIDTH/2,HEIGHT-150)
        text("count  "..cnt,WIDTH/2,HEIGHT-200)
        text("max  FPS  "..fmax,WIDTH/2,120)
        text("curr FPS  "..fps,WIDTH/2,100)
        text("min  FPS  "..fmin,WIDTH/2,80)
    
    end
    
    function touched(t)
        if t.state==BEGAN then
            setup()
        end
    end
    
  • Posts: 256

    @dave1707 The issue I have with monitors like that is that they only end up showing an fps of 60 or 30fps, not a ‘true’ framerate of ‘this is how many frames completed in the last second’.

    It’s unfortunately due to the Codea backend missing the display’s vsync interval in which we can do the backbuffer swap. When we miss a vsync interval the next one is a whole frame away so we end up waiting for that and DeltaTime doubles from ~0.01666 to ~0.03333.

  • Posts: 256

    @Simeon I feel like this must be due to the preferredFPS changes a while ago.

    I have a feeling you may be waiting for the intended frametime to pass fully in a single wait call which isn’t particularly reliable? If that is the case could we do it in a loop waiting for a factor of the remaining time each loop?

  • dave1707dave1707 Mod
    Posts: 9,730

    @Steppers That’s correct. I noticed long ago that as the fps gets bogged down, it decreases in steps. Codea is probably set at 60 fps (120 in newer devices) so when it misses a frame, it waits until the next time it should display a frame and not displaying when it could display a frame. Unless there’s a big demand on the system, it probably doesn’t really matter if it runs at 60 or 50 or maybe a lower fps because depending on what you’re doing, you might not notice it.

  • Posts: 256
    @dave1707 Unfortunately this seems pretty bad, not like it's just occasionally missing a frame. Running the bare fps overlay I posted earlier I saw as low as 12 fps for an extended period of time :neutral:

    Otherwise you are right, it shouldn't make much of a difference if we run at 55fps but the question is why we are at 55 fps in the first place? It just seems very unstable and unpredictable.
  • dave1707dave1707 Mod
    Posts: 9,730

    @Steppers I put a print statement in your if statement to print the FPS and commented the assert statement. It ran pretty much at 60 FPS. I rotated the iPad so it had something to do and it dropped to 59 or 58 for one print then back to 60. According to the documentation, DeltaTime is the time between draw cycles. So you’re using the time between draw cycles to check draw cycles. You’re getting an assert error on the first check which probably isn’t reliable because it’s just starting the calcs.

    If you want an independent time, use the socket gettime command. You have to do a require to get the socket code. That should give you a good time that’s shouldn’t be affected by Codea.

    s=require(“socket”)
    t=s:gettime()

  • Posts: 256
    @dave1707 Oh sorry, I meant FPSOverlay.zip with the graph display using a far more accurate technique. It's perfectly capable of running at 60 fps with no dropped frames but other times the frame rate is all over the place, down to as low as 12 fps yesterday doing nothing but drawing a graph.
  • dave1707dave1707 Mod
    Posts: 9,730

    @Steppers Here’s another one I put together. It uses the socket gettime. Every time the draw function is called, I get the socket time and that gets subtract from the previous time. So what I have is the time between draw calls. I divide that into 1 to get the FPS for that call. Tap the screen to cause changes in the times.

    viewer.mode=FULLSCREEN
    viewer.retainedBacking=true
    
    function setup()
        fill(255)
        s=require("socket")
        h=s:gettime()
        x=0
        stroke(255)
        strokeWidth(1)
        fill(255)
        dh=0
    end
    
    function draw()
        if x>WIDTH then
            background(0)    
            x=0
        end
        stroke(255,0,0)
        if x==0 then
            for r=-300,300,100 do
                line(0,HEIGHT/2+r,WIDTH,HEIGHT/2+r)
                text(60+r/20,WIDTH/2,HEIGHT/2+r)
            end
        end
    
        t=s:gettime()
        diff=1/(t-h)
    
        stroke(255)
        if x>10 then
            line(x,HEIGHT/2+(diff-60)*20,x-2,HEIGHT/2+(dh-60)*20)
        end
    
        x=x+2
        dh=diff
        h=t  
    end
    
Sign In or Register to comment.