1
\$\begingroup\$

I have tried achieving a good delta time and fps counter over the last few days, read and watched a lot about it, but still can't seem to get it to work.

Here is an example:

#include <iostream>
#include <SDL.h>
#include <SDL_image.h>

int main(int argc, char* argv[]) {

    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        printf("error initializing SDL: %s\n", SDL_GetError());
    }

    SDL_Window* window = SDL_CreateWindow("Title", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1000, 900, SDL_WINDOW_SHOWN);

    if (!window) {
        printf("error creating window: %s\n", SDL_GetError());
    }

    Uint32 renderFlags = SDL_RENDERER_ACCELERATED;
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, renderFlags);
    if (!renderer) {
        printf("error creating renderer");
        SDL_DestroyWindow(window);
        SDL_Quit();
    }

    SDL_Surface* surface = IMG_Load("dot.bmp");

    if (!surface) {
        printf("Error creating surface: %s\n", SDL_GetError());
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        SDL_Quit();
    }

    SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
    SDL_FreeSurface(surface);
    if (!texture) {
        printf("error creating texture: %s\n", SDL_GetError());
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        SDL_Quit();
    }

    SDL_Rect dest;

    dest.x = 0;
    dest.y = 0;

    SDL_QueryTexture(texture, NULL, NULL, &dest.w, &dest.h);

    Uint64 NOW = SDL_GetPerformanceCounter();
    Uint64 LAST = 0;
    double deltaTime = 0;

    while (true) {
        LAST = NOW;
        NOW = SDL_GetPerformanceCounter();
        deltaTime = (double)((NOW - LAST) / (double)SDL_GetPerformanceFrequency());
        
        SDL_RenderClear(renderer);

        dest.x += 50 * deltaTime;
        dest.y += 50 * deltaTime;

        SDL_RenderCopy(renderer, texture, NULL, &dest);

        SDL_RenderPresent(renderer);    

        std::cout << "Delta Time: " << deltaTime << std::endl;
        std::cout << "FPS: " << 60.0 - deltaTime << std::endl;

        SDL_Delay(1000.0f / (60.0 - deltaTime));
    }


    return 0;
}

I used the suggestion from this post: How to calculate delta time with SDL?

I print out "delta time" and "FPS" to the console and, while the deltaTime is slightly different each time, the FPS is stable 60 (which is the same value that I use in SDL_Delay to calculate delay in ms). But the test image is clearly moving not smoothly, it stutters and moves at inconsistent speed, and I can't understand why.

Please help me understand what I am doing wrong. I just can't understand it even after looking through many examples.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ I can't offer an answer to your question but here are a couple of ways you're thrashing your frame time: the doc for SDL_Delay states It waits at least the specified time, but possibly longer due to OS scheduling. You might want to use a busy loop instead. Those cout are not trivial and take up some time in your frame time (possibly waiting for an OS thread); you might want to print those out only once per second. Also, if you expect your deltaTime to be 1/60th of a second, just pass that as your velocity parameter in (dest.x += 50 * deltaTime;), that might help. \$\endgroup\$
    – Vaillancourt
    Commented May 3, 2021 at 18:13

1 Answer 1

1
\$\begingroup\$

I use a loop based on https://gafferongames.com/post/fix_your_timestep/
It renders as fast as possible and only updates when enough time has passed. This keeps your update code incredibly simple because you can assume a preset amount of time has passed. In the update function you cache the last state and calculate the new state, in the render function you calculate an interpolated state based on how much time is left which determines "where" in between the two states you're rendering. Then you render that interpolated state.

namespace Time {
    using namespace std::chrono;
    
    // get current time
    inline auto now() {
        return high_resolution_clock::now();
    }
    
    // get t as milliseconds
    inline auto step(const unsigned long long int t) {
        return milliseconds(t);
    }
    
    // get t as nanoseconds
    inline auto lag(const unsigned long long int t) {
        using namespace std::chrono_literals;
        return nanoseconds(0ns) + step(t);
    }
    
    // get delta time
    inline auto delta(const time_point<high_resolution_clock>& t0) {
        return duration_cast<nanoseconds>(now() - t0);
    }
}

int run(unsigned long long int t) {
    // update step in milliseconds
    const auto step = Time::step(t);
    // update step in nanoseconds
    const float step_ns = (float) step.count() * 1000000;
    // init lag exactly at step to force the first update cycle
    auto lag = Time::lag(step.count());
    // current time
    auto t0 = Time::now();
    while (true) {
        while (lag >= step) {
            //update(t);
            lag -= step;
        }
        auto alpha = (float) lag.count() / step_ns;
        //render(alpha);
        // update lag and current time
        lag += Time::delta(t0);
        t0 = Time::now();
    }
    return 0;
}

Say you have an object at 0,0. Moving it horizontally across the screen at an intended speed of 10 units per millisecond is easy as:

update(unsigned long long int) {
    laststate = newstate;
    newstate.obj.x += (10 * t);`
}
render(float alpha) {
    auto objX = laststate.obj.x + ((newstate.obj.x - laststate.obj.x) * alpha);
    auto objY = laststate.obj.y + ((newstate.obj.y - laststate.obj.y) * alpha);
    // render the object using the interpolated x and y
}

The interpolation calculation can vary from property to property, so don't put it in the render function like I did here, but create a new state object based on the two cached states and the alpha value. This is just an example.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .