-1

I am using SDL 2.0.4 and GLEW 1.13.0 and it seems like there is some kind of fps limit in SDL2. I know this, because I did some time measurement.

To be more specific, I do not draw anything, but if I call SDL_GL_SwapWindow() in my main loop, each cycle takes about 16 milliseconds. Without this call it takes almost no time at all. It even takes 16 milliseconds, when SDL_GL_SwapWindow is the only function call in the main loop. This means, that there has to be some kind of fps limitation or vsync enabled in SDL2. So my question is: How do I disable said limitation?

The only thread I found, that somewhat closely resembles my question, is the following: SDL_GL_SwapWindow bad performance . But the answers in this thread don't really are of help to me. Furthermore, this doesn't seem to be caused by my computer, because the issue doesn't occur on similar code done with FreeGLUT.

Brief overview over all files:

jtime.h:       For time measurement
color.h:       For colored console output (easier to make out errors)
display.h:     Declaration of class Display
display.cpp:   Implementation of class Display
main.cpp:      Main function and HandleEvents()

My Code:

main.cpp

#include <iostream>
#include <conio.h>

#include <display.h>
#include <glew.h>
#include <jtime.h>

void HandleEvents(SDL_Event&& e, jpk::Display& d)
{
    if(e.type == SDL_QUIT)
        d.GetWindowState() = jpk::Display::WindowState::EXIT;
}

int main(int argc, char* argv[])
{
    jpk::Display display("Test", SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED, 400, 400);

    if(display.GetWindowState() == jpk::Display::WindowState::EXIT)
        return 1;
    else
    {
        jpk::measure<> ms;
        while(display.GetWindowState() != jpk::Display::WindowState::EXIT)
        {
            ms.start();
            display.Update(50, HandleEvents);
            std::cout << "Time taken: " << ms.stop() << "ms" << std::endl;
        }
        return 0;
    }
}

display.cpp

#include <display.h>
#include <color.h>
#include <jtime.h>

#include <glew.h>
#include <wglew.h>
#include <iostream>
#include <vector>
#include <list>

jpk::Display::SDL2Helper jpk::Display::helper(SDL_INIT_MODULES, SDL_OUTPUT_MSG);

jpk::Display::SDL2Helper::SDL2Helper(const uint32_t& f, const bool& o) :
    init(true)
{
    jpk::measure<> ms;
    if(SDL_Init(f))
    {
        std::cerr << jpk::color::light_red_f << "[Error] SDL2 could not be "
                "initialized.\nDetails according to SDL2: " << SDL_GetError() <<
                jpk::color::reset << std::endl;
        init = false;
    }
    else if(o)
        std::cout << jpk::color::light_green_f << "[Info] SDL2 has been " <<
                "initialized successfully. Time: " << ms.stop() << "ms." <<
                jpk::color::reset << std::endl;
}

jpk::Display::SDL2Helper::~SDL2Helper(void)
{
    if(init)
        SDL_Quit();
}

jpk::Display::Display(const std::string& t, const unsigned int& x,
                      const unsigned int& y, const unsigned int& w,
                      const unsigned int& h, const uint32_t& f, std::string s) :
                      window(nullptr),
                      glContext(nullptr),
                      state(WindowState::READY)
{
    jpk::measure<> ms;

    window = SDL_CreateWindow(t.c_str(), x, y, w, h, f | SDL_WINDOW_OPENGL |
            SDL_WINDOW_HIDDEN);

    if(window == nullptr)
    {
        std::cerr << jpk::color::light_red_f << "[Error] The instance of " <<
                "class 'Display' couldn't be created.\nDetails according to " <<
                "SDL2: " << SDL_GetError() << jpk::color::reset <<
                std::endl;

        state = WindowState::EXIT;
    }
    else
    {
        glContext = SDL_GL_CreateContext(window);

        if(glContext == nullptr)
        {
            std::cerr << jpk::color::light_red_f << "[Error] The GL Context " <<
                    "of the instance of class 'Display' couldn't be " <<
                    "initialized.\nDetails according to SDL2: " <<
                    SDL_GetError() << jpk::color::reset << std::endl;
            state = WindowState::EXIT;
        }
        else
        {
            GLenum error(glewInit());

            if(error != GLEW_OK)
            {
                std::cerr << jpk::color::light_red_f << "[Error] GLEW " <<
                        "failed to initialize.\nDetails according to GLEW: " <<
                        glewGetErrorString(error) << jpk::color::reset <<
                        std::endl;
                state = WindowState::EXIT;
            }
            else
            {
                bool noSupport(false);
                if(s.length() > 0)
                {
                    s += " ";
                    size_t found(s.find(" "));

                    while(found != std::string::npos)
                    {
                        std::string ext(s);

                        ext.erase(found);
                        s.erase(0, found+1);

                        if(!glewIsSupported(ext.c_str()) &&
                                !wglewIsSupported(ext.c_str()))
                        {
                            std::cout << jpk::color::light_red_f << "[Error] " <<
                                    "The following GLEW extension is not " <<
                                    "supported: " << ext << "." <<
                                    jpk::color::reset << std::endl;
                            noSupport = true;
                        }
                        found = s.find(" ");
                    }
                }

                if(!noSupport)
                {
                    std::cout << jpk::color::light_green_f << "[Info] The " <<
                            "instance of class 'Display' has successfully " <<
                            "been created! Time: " << ms.stop() << "ms." <<
                            jpk::color::reset << std::endl;

                    if(!(f & SDL_WINDOW_HIDDEN))
                        SDL_ShowWindow(window);
                }
                else
                    state = WindowState::EXIT;
            }
        }
    }
}

jpk::Display::~Display(void)
{
    if(glContext != nullptr)
        SDL_GL_DeleteContext(glContext);
    if(window != nullptr)
        SDL_DestroyWindow(window);
}

bool jpk::Display::SDL_InitStatus(void)
{
    return helper.init;
}

void jpk::Display::Update(const unsigned int& n, void (*f)(SDL_Event&&, jpk::Display&))
{
    SDL_GL_SwapWindow(window);
    static std::list<SDL_Event> events;

    SDL_Event e;
    while(SDL_PollEvent(&e))
        events.push_back(e);

    if(n != 0)
        for(unsigned int i(0); i < n ;i++)
        {
            f(std::move(events.front()), *this);
            events.pop_front();
        }
    else
    {
        const unsigned int numEvents(events.size());
        for(unsigned int i(0); i < numEvents ;i++)
        {
            f(std::move(events.front()), *this);
            events.pop_front();
        }
    }
}

void jpk::Display::Show(void)
{
    if(window != nullptr)
        SDL_ShowWindow(window);
}

void jpk::Display::Hide(void)
{
    if(window != nullptr)
        SDL_HideWindow(window);
}

jpk::Display::WindowState& jpk::Display::GetWindowState(void)
{
    return state;
}

display.h

#ifndef DISPLAY_H
#define DISPLAY_H

#define SDL_MAIN_HANDLED

#define SDL_INIT_MODULES SDL_INIT_EVERYTHING
#define SDL_OUTPUT_MSG false

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

namespace jpk
{
    class Display
    {
    public:
        enum class WindowState
        {
            READY,
            EXIT
        };

        Display(const std::string& title, const unsigned int& pos_x,
                const unsigned int& pos_y, const unsigned int& width,
                const unsigned int& height, const uint32_t& flags = 0,
                std::string support = "");
        ~Display(void);

        Display(const Display&) = delete;
        Display& operator=(const Display&) = delete;

        static bool SDL_InitStatus(void);
        WindowState& GetWindowState(void);

        void Update(const unsigned int& numEvents,
                void (*eventFunc)(SDL_Event&&, jpk::Display&));

        void Show(void);
        void Hide(void);

    private:
        struct SDL2Helper
        {
            SDL2Helper(const uint32_t& flags, const bool& output = true);
            ~SDL2Helper(void);

            SDL2Helper(const SDL2Helper&) = delete;
            SDL2Helper& operator=(const SDL2Helper&) = delete;

            bool init;
        };

        SDL_Window* window;
        SDL_GLContext glContext;
        WindowState state;
        static SDL2Helper helper;
    };
}

#endif /* DISPLAY_H */

jtime.h

#ifndef JTIME_H
#define JTIME_H

#include <chrono>
#include <utility>

#ifndef CLOCK_TYPE
#define CLOCK_TYPE std::chrono::steady_clock
#endif // CLOCK_TYPE

namespace jpk
{
    template<typename TimeT = std::chrono::milliseconds> class measure
    {
    public:
        measure(void) :
            t(CLOCK_TYPE::now())
        {}
        ~measure(void) {}

        measure(const measure&) = delete;
        measure& operator=(const measure&) = delete;

        void start(void)
        {
            t = CLOCK_TYPE::now();
        }

        typename TimeT::rep stop(void)
        {
            return std::chrono::duration_cast<TimeT>(CLOCK_TYPE::now()-t).count();
        }

        TimeT stop_chrono(void)
        {
            return std::chrono::duration_cast<TimeT>(CLOCK_TYPE::now()-t);
        }

        template<typename F, typename... Args> static
            typename TimeT::rep duration_single(F func, Args&&... args)
        {
            auto start(CLOCK_TYPE::now());
            std::forward<decltype(func)>(func)(std::forward<Args>(args)...);
            return std::chrono::duration_cast<TimeT>(CLOCK_TYPE::now()-start).count();
        }

        template<typename F, typename... Args> static typename TimeT::rep
            duration_average(const unsigned int& tries, F func, Args&&... args)
        {
            typename TimeT::rep* times = new typename TimeT::rep[tries];
            typename TimeT::rep time(0.0);

            for(unsigned int i(0); i < tries ;i++)
                times[i] = duration_single(func, args...);

            for(unsigned int i(0); i < tries ;i++)
                time += times[i];

            delete[] times;
            return double(time)/double(tries);
        }

        template<typename F, typename... Args> static
            TimeT duration_chrono(F func, Args&&... args)
        {
            auto start(CLOCK_TYPE::now());
            std::forward<decltype(func)>(func)(std::forward<Args>(args)...);
            return std::chrono::duration_cast<TimeT>(CLOCK_TYPE::now()-start);
        }

    private:
        CLOCK_TYPE::time_point t;
    };

    template<typename TimeT = std::chrono::milliseconds>
        void wait(const typename TimeT::rep& time)
    {
        CLOCK_TYPE::time_point start(CLOCK_TYPE::now());
        while(std::chrono::duration_cast<TimeT>(CLOCK_TYPE::now() - start).count()
                 < time)
        {
            // Just stop doing anything
        }
    }
}

#endif /* JTIME_H */

color.h

#ifndef COLOR_H
#define COLOR_H

#include <string>
#include <iostream>

#if defined(_WIN32) && !defined(JPK_USE_ANSI)
#include <windows.h>
#endif // _WIN32

namespace jpk
{
    class color_t
    {
    public:
        color_t(const unsigned int& col);
#if !defined(_WIN32) || defined(JPK_USE_ANSI)
        color_t(const std::string& esc);
#endif // _WIN32
        virtual ~color_t(void);

        color_t(const color_t&) = delete;
        color_t& operator=(const color_t&) = delete;

        void use(std::ostream& out) const;
        friend std::ostream& operator<<(std::ostream&, const jpk::color_t&);

    private:
#if defined(_WIN32) && !defined(JPK_USE_ANSI)
        const unsigned int c;

        static bool reset_attr_got;
        static WORD reset_attr;
#else
        const std::string seq;
#endif // _WIN32
    };

    struct color
    {
        enum colors
        {
            BLACK_F,
            BLUE_F,
            GREEN_F,
            CYAN_F,
            RED_F,
            MAGENTA_F,
            BROWN_F,
            GREY_F,
            DARKGREY_F,
            LIGHTBLUE_F,
            LIGHTGREEN_F,
            LIGHTCYAN_F,
            LIGHTRED_F,
            LIGHTMAGENTA_F,
            YELLOW_F,
            WHITE_F,

            BLACK_B,
            BLUE_B,
            GREEN_B,
            CYAN_B,
            RED_B,
            MAGENTA_B,
            YELLOW_B,
            WHITE_B,

            RESET
        };

        color(void) = delete;
        ~color(void) = delete;

        static color_t black_f;
        static color_t red_f;
        static color_t green_f;
        static color_t brown_f;
        static color_t blue_f;
        static color_t magenta_f;
        static color_t cyan_f;
        static color_t grey_f;
        static color_t dark_grey_f;
        static color_t light_red_f;
        static color_t light_green_f;
        static color_t yellow_f;
        static color_t light_blue_f;
        static color_t light_magenta_f;
        static color_t light_cyan_f;
        static color_t white_f;

        static color_t black_b;
        static color_t red_b;
        static color_t green_b;
        static color_t yellow_b;
        static color_t blue_b;
        static color_t magenta_b;
        static color_t cyan_b;
        static color_t white_b;

        static color_t reset;
    };
}

#if !defined(_WIN32) || defined(JPK_USE_ANSI)
    std::string getAnsiEsc(const unsigned int& col)
    {
        switch(col)
        {
        case jpk::color::BLACK_F:           return "\033[22;30m";
        case jpk::color::RED_F:             return "\033[22;31m";
        case jpk::color::GREEN_F:           return "\033[22;32m";
        case jpk::color::BROWN_F:           return "\033[22;33m";
        case jpk::color::BLUE_F:            return "\033[22;34m";
        case jpk::color::MAGENTA_F:         return "\033[22;35m";
        case jpk::color::CYAN_F:            return "\033[22;36m";
        case jpk::color::GREY_F:            return "\033[22;37m";
        case jpk::color::DARKGREY_F:        return "\033[01;30m";
        case jpk::color::LIGHTRED_F:        return "\033[01;31m";
        case jpk::color::LIGHTGREEN_F:      return "\033[01;32m";
        case jpk::color::YELLOW_F:          return "\033[01;33m";
        case jpk::color::LIGHTBLUE_F:       return "\033[01;34m";
        case jpk::color::LIGHTMAGENTA_F:    return "\033[01;35m";
        case jpk::color::LIGHTCYAN_F:       return "\033[01;36m";
        case jpk::color::WHITE_F:           return "\033[01;37m";

        case jpk::color::BLACK_B:           return "\033[40m";
        case jpk::color::RED_B:             return "\033[41m";
        case jpk::color::GREEN_B:           return "\033[42m";
        case jpk::color::YELLOW_B:          return "\033[43m";
        case jpk::color::BLUE_B:            return "\033[44m";
        case jpk::color::MAGENTA_B:         return "\033[45m";
        case jpk::color::CYAN_B:            return "\033[46m";
        case jpk::color::WHITE_B:           return "\033[47m";

        case jpk::color::RESET:             return "\033[0m";
        }
        return "";
    }
#endif // _WIN32

jpk::color_t::color_t(const unsigned int& col) :
#if defined(_WIN32) && !defined(JPK_USE_ANSI)
        c(col)
    {
        if(!reset_attr_got)
        {
            CONSOLE_SCREEN_BUFFER_INFO csbi;
            GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);

            reset_attr = csbi.wAttributes;
            reset_attr_got = true;
        }
#else
        seq(jpk::getAnsiEsc(col))
    {}

    jpk::color_t::color_t(const std::string& esc) :
        seq(esc)
    {
#endif // _WIN32
}

jpk::color_t::~color_t(void) {}

#if defined(_WIN32) && !defined(JPK_USE_ANSI)
    bool jpk::color_t::reset_attr_got(false);
    WORD jpk::color_t::reset_attr(0);

    void jpk::color_t::use(std::ostream& out) const
    {
        if(c <= jpk::color::RESET)
        {
            HANDLE hConsole(GetStdHandle(STD_OUTPUT_HANDLE));
            CONSOLE_SCREEN_BUFFER_INFO csbi;
            GetConsoleScreenBufferInfo(hConsole, &csbi);

            if(c < jpk::color::BLACK_B)
                SetConsoleTextAttribute(hConsole, (csbi.wAttributes & 0xFFF0) | (WORD)c);
            else if((c > jpk::color::WHITE_F) && (c < jpk::color::RESET))
                SetConsoleTextAttribute(hConsole, (csbi.wAttributes & 0xFF0F) | (((WORD)(c - jpk::color::BLACK_B)) << 4));
            else if(c == jpk::color::RESET)
                SetConsoleTextAttribute(hConsole, reset_attr);
        }
    }

    jpk::color_t jpk::color::black_f(jpk::color::BLACK_F);
    jpk::color_t jpk::color::red_f(jpk::color::RED_F);
    jpk::color_t jpk::color::green_f(jpk::color::GREEN_F);
    jpk::color_t jpk::color::brown_f(jpk::color::BROWN_F);
    jpk::color_t jpk::color::blue_f(jpk::color::BLUE_F);
    jpk::color_t jpk::color::magenta_f(jpk::color::MAGENTA_F);
    jpk::color_t jpk::color::cyan_f(jpk::color::CYAN_F);
    jpk::color_t jpk::color::grey_f(jpk::color::GREY_F);
    jpk::color_t jpk::color::dark_grey_f(jpk::color::DARKGREY_F);
    jpk::color_t jpk::color::light_red_f(jpk::color::LIGHTRED_F);
    jpk::color_t jpk::color::light_green_f(jpk::color::LIGHTGREEN_F);
    jpk::color_t jpk::color::yellow_f(jpk::color::YELLOW_F);
    jpk::color_t jpk::color::light_blue_f(jpk::color::LIGHTBLUE_F);
    jpk::color_t jpk::color::light_magenta_f(jpk::color::LIGHTMAGENTA_F);
    jpk::color_t jpk::color::light_cyan_f(jpk::color::LIGHTCYAN_F);
    jpk::color_t jpk::color::white_f(jpk::color::WHITE_F);

    jpk::color_t jpk::color::black_b(jpk::color::BLACK_B);
    jpk::color_t jpk::color::red_b(jpk::color::RED_B);
    jpk::color_t jpk::color::green_b(jpk::color::GREEN_B);
    jpk::color_t jpk::color::yellow_b(jpk::color::YELLOW_B);
    jpk::color_t jpk::color::blue_b(jpk::color::BLUE_B);
    jpk::color_t jpk::color::magenta_b(jpk::color::MAGENTA_B);
    jpk::color_t jpk::color::cyan_b(jpk::color::CYAN_B);
    jpk::color_t jpk::color::white_b(jpk::color::WHITE_B);

    jpk::color_t jpk::color::reset(jpk::color::RESET);
#else
    void jpk::color_t::use(std::ostream& out) const
    {
        out << seq;
    }

    jpk::color_t jpk::color::black_f("\033[22;30m");
    jpk::color_t jpk::color::red_f("\033[22;31m");
    jpk::color_t jpk::color::green_f("\033[22;32m");
    jpk::color_t jpk::color::brown_f("\033[22;33m");
    jpk::color_t jpk::color::blue_f("\033[22;34m");
    jpk::color_t jpk::color::magenta_f("\033[22;35m");
    jpk::color_t jpk::color::cyan_f("\033[22;36m");
    jpk::color_t jpk::color::grey_f("\033[22;37m");
    jpk::color_t jpk::color::dark_grey_f("\033[01;30m");
    jpk::color_t jpk::color::light_red_f("\033[01;31m");
    jpk::color_t jpk::color::light_green_f("\033[01;32m");
    jpk::color_t jpk::color::yellow_f("\033[01;33m");
    jpk::color_t jpk::color::light_blue_f("\033[01;34m");
    jpk::color_t jpk::color::light_magenta_f("\033[01;35m");
    jpk::color_t jpk::color::light_cyan_f("\033[01;36m");
    jpk::color_t jpk::color::white_f("\033[01;37m");

    jpk::color_t jpk::color::black_b("\033[40m");
    jpk::color_t jpk::color::red_b("\033[41m");
    jpk::color_t jpk::color::green_b("\033[42m");
    jpk::color_t jpk::color::yellow_b("\033[43m");
    jpk::color_t jpk::color::blue_b("\033[44m");
    jpk::color_t jpk::color::magenta_b("\033[45m");
    jpk::color_t jpk::color::cyan_b("\033[46m");
    jpk::color_t jpk::color::white_b("\033[47m");

    jpk::color_t jpk::color::reset("\033[0m");
#endif // _WIN32

namespace jpk
{
    std::ostream& operator<<(std::ostream& out, const color_t& col)
    {
        col.use(out);
        return out;
    }
}

#endif /* COLOR_H */
Community
  • 1
  • 1
JPKing64
  • 15
  • 4

1 Answers1

1

This has nothing to do with SDL but with the OpenGL settings of your graphics driver. When swapping buffers the operation may be synchronized to the display refresh, so that no tearing artefacts appear. If your program is drawing faster than the display is able to redraw then within a single displayed frame several "stripes" of rendering will appear, each delayed by the time it took the render the first one. Hence in general you actually _want_ your redraw to be FPS limited and synchronized to the display refresh. There are a number of APIs to selectively enable or disable it from within a program (though the driver can always opt to override that). In case of OpenGL (which is the API SDL_GL_SwapBuffers) goes through the API to control this is the so called "swap interval" API. Details can be found in the OpenGL wiki: https://www.opengl.org/wiki/Swap_Interval

datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • Okay then, but why does this occur when using SDL2, but not when using FreeGLUT to do the exact same thing? Does FreeGLUT disable it? – JPKing64 Oct 02 '16 at 17:15
  • @JPKing64: SDL2 actually "knows" about the swap interval API (see https://wiki.libsdl.org/SDL_GL_SetSwapInterval ) and simply might set it to a specific initialization value, whereas FreeGLUT does not have wrappers for the swap interval API and leaves it "as is" after creating a context. – datenwolf Oct 02 '16 at 19:08
  • Thanks for the help. After some further investigation, I found out how it works. If there still is time, SDL2 will simply do nothing, until it is time to display the new image. Even if I wait for 3ms every cycle, the fps are still kept at 60 most of the time. To be perfectly honest, I will just let SDL2 do it's thing for now. But I thought, that maybe at some point in the future, it might come in handy to disable the fps limitation, so I will still implement the option to disable the swap interval in my Display class. – JPKing64 Oct 02 '16 at 19:37
  • @JPKing64: It's not really SDL that's doing something there (besides setting the swap interval to 1 by default). What's limiting your refresh rate is actually the display device (i.e. your monitor). The GPU is simply waiting for the display to scan out a full image before swapping the buffers. Also it's not written in stone at which point the wait is actually going to happen. NVidia usually waits in swap buffers. AMD normally, too. But Intel is all over the place and all that can be known with Intel that it will limit frame updates to display refresh by waiting in *some* OpenGL function. – datenwolf Oct 02 '16 at 20:57
  • @JPKing64: The problem is, that nobody did actually bother to write down how buffer swaps are to be timed. Every implementation is left to its own devices. I had to learn that, when starting VR stuff. Lots of "WTF?!" were exclaimed when I did accurate time measurements (that in fact involved oscilloscopes hooked up to display signals and [Super I/O chip](https://en.wikipedia.org/wiki/Super_I/O) ports). – datenwolf Oct 02 '16 at 21:01