Question about RTSS frame limiting implementation

Discussion in 'Rivatuner Statistics Server (RTSS) Forum' started by Ryan Powser, Dec 24, 2022.

  1. Ryan Powser

    Ryan Powser Guest

    Messages:
    2
    Likes Received:
    0
    GPU:
    MSI RTX 3070
    Hi, I'm writing gameboy emulator in c++ with opengl thru SDL2 and I'm trying to get consistent frame times.

    Currently it's limited to 60fps but my frame times are all over the place. So technically it averages 60fps but it looks horribly juddery. I've tried several ways of implementing an fps limit and none can seem to get me consistent frame times.

    The current implementation I'm using is a busy wait at the end of the game loop while chrono::steady_clock::now() is less than the start time of the next frame. The next frame time is calculated at the beginning of the game loop as now() + duration<uint64_t, ratio<1, 60>>. I've tried this with and without vsync, and with and without gsync, all of which result in no difference to frame times. I am running on a 144hz freesync monitor thats limited to 120hz.

    If I remove my limiter code and instead limit fps using RTSS, I get perfect frame times. So I'm wondering how RTSS does it. I'm looking for as much detail as possible, ideally code snippets or pseudo code, so I can implement something similar in my emulator and achieve the desired behavior.

    Thanks in advance.
     
  2. Unwinder

    Unwinder Ancient Guru Staff Member

    Messages:
    17,208
    Likes Received:
    6,894
    Hi

    I'm not a fan of using modern C++ chrono library for timing purposes so I cannot comment chrono::steady_clock implementation specifics for sure, but most likely this timer implementation is platform specific and it has too low resolution for your needs. Try to switch to QueryPerformanceFrequency/QueryPerformanceCounter for timing to be sure that you use the maximum possible timing precision.

    RTSS implementation is not a rocket science, it uses QPF/QPC to timestamp frames and waits for target timestamp either immediately before presenting the frame (if front [Present] edge sync mode is selected for framerate limiter) or immediately after presenting the frame (if back [Present] edge sync or async mode is selected).
     
    Dan Longman likes this.
  3. lawood

    lawood Member Guru

    Messages:
    199
    Likes Received:
    110
    GPU:
    RTX 3070 Ti
    In my experience RTSS IS above and beyond any other method to limiting frames, i get the exact same experience on normal games.
    its not unplayble without rtss like with your case but the frametimes are much more steady.
    it does add inputlag but its very hard to notice. use it on all games.
     
  4. Astyanax

    Astyanax Ancient Guru

    Messages:
    17,045
    Likes Received:
    7,382
    GPU:
    GTX 1080ti
    why not just look over the C+ source for mgba.
     

  5. Ryan Powser

    Ryan Powser Guest

    Messages:
    2
    Likes Received:
    0
    GPU:
    MSI RTX 3070
    Ok so what I used to have was basically this:
    Code:
    int DesiredFPS = 60;
    
    using clock = std::chrono::high_resolution_clock;
    using frames = std::chrono::duration<double, std::ratio<1, DesiredFPS>>;
    
    auto nextFrame = clock::now() + frames{ 0 };
    
    while (true)
    {
        nextFrame += frames{ 1 };
    
        // poll window events
        // run emulation
        // render and present
    
        decltype(nextFrame) i = clock::now();
        while (i < nextFrame)
        {
            i = clock::now();
        }
    }
    
    This code produces these frame time graphs measured with RTSS:
    emulator idle upload_2022-12-24_14-29-23.png

    emulator running upload_2022-12-24_14-29-5.png


    If I manually limit frame rate with rtss I get this graph which is the desired behavior: upload_2022-12-24_14-29-49.png


    Obviously, the above is not a good solution. :(


    What I've got now, based on your suggestions is basically the same code but using QPF and QPC instead of the chrono library:
    Code:
    int DesiredFPS = 60;
    LARGE_INTEGER nextFrame;
    LARGE_INTEGER PerformanceFrequency;
    QueryPerformanceFrequency(&PerformanceFrequency);
    
    while (true)
    {
        QueryPerformanceCounter(&nextFrame);
        nextFrame.QuadPart += (PerformanceFrequency.QuadPart / DesiredFPS);
    
        // poll window events
        // run emulation
        // render and present
    
        decltype(nextFrame) i = {};
        QueryPerformanceCounter(&i);
        while (i.QuadPart < nextFrame.QuadPart)
        {
            QueryPerformanceCounter(&i);
        }
    }
    
    Unfortunately this code produces identical results to the chrono code. I'm really at my wits end here...
     

Share This Page