SDL2/OpenGL Threads



  • Einen guten Morgen wünsche ich,

    ich setze mich im Moment ein wenig mit C + SDL2 + OpenGL auseinander und habe auch ein wenig recherchiert, ob ich das Rendering in einen anderen Thread verschieben kann.

    In mehreren Einträgen die ich gelesen habe, viel davon in SO, wird immer nur darauf hingewiesen, dass es unsinnig sei und kein Performance Gewinn dabei heraus kommt, wenn man das Rendering auf Threads auslagert. Für mich hat es sich so gelesen, dass man jedoch versucht hat, von mehreren Threads in einen OpenGL Context zu rendern. Was für mich auch keinen Sinn macht.

    Ich möchte einfach das rendern vollständig in einen anderen Thread schieben, damit der Mainloop immer responsiv bleibt, selbst wenn der Renderthread etwas langsamer ist.

    Ich habe es auch geschafft, das rendern in einen anderen Thread zu schieben, die Frage ist jedoch: Sollte ich das überhaupt tun? In einer Quelle die ich gelesen habe (habe den Link aktuell nicht mehr, kann ich aber nochmal suchen falls gewünscht) wird behauptet, dass ein arbeiten mit der OpenGL Bibliothek außerhalb des Main Threads zu unerwartetem Verhalten führen kann.

    Ich konnte bisher mit einem kleinen Programm keine Probleme feststellen auf einer Debian Distribution mit OpenGL 2.1 (siehe weiter unten).

    Ich bin gespannt auf das Feedback. Danke im Voraus für die Mühe!

    #include <stdlib.h>
    #include <stdbool.h>
    #include <wchar.h>
    #include <locale.h>
    #include <stdint.h>
    #include <pthread.h>
    #include <GL/gl.h>
    #include <SDL2/SDL.h>
    #include <SDL2/SDL_image.h>
    #include <SDL2/SDL_ttf.h>
    /* Threading test */
    #include <SDL2/SDL_thread.h>
    
    typedef struct _data {
      SDL_Window *win;
      SDL_Event *event;
      bool running;
      float *r;
      float *g;
      float *b;
    } data_t;
    
    void logSDLError( const char *msg, const char *function_name )
    {
      fprintf( stderr, "[%s] Error: %s\n\tSDL: %s\n", function_name, msg, SDL_GetError() );
    }
    
    SDL_Window *Init_GL_Window( const char *window_title, const unsigned int size_x, const unsigned int size_y )
    {
      /*First of all, Init SDL Systems*/
      if( SDL_Init( SDL_INIT_EVERYTHING ) < 0 || TTF_Init() < 0 )
      {
        logSDLError( "Init of Systems failed.", __FUNCTION__ );
        return NULL;
      }
      /*Setup the OpenGL for SDL*/
      SDL_Window *tmp = SDL_CreateWindow( window_title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, \
        size_x, size_y, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN );
      if( tmp == NULL )
      {
        logSDLError( "Could not create Window", __FUNCTION__ );
        return tmp;
      }
      int major = 0;
      int minor = 0;
      SDL_GL_GetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, &major );
      SDL_GL_GetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, &minor );
      printf( "Default GL Version: %i.%i\n", major, minor );
      /* Core Profile for OpenGL */
      SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE );
      /* Doublebuffer */
      SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
      /* Sync with Vtrace */
      SDL_GL_SetSwapInterval( 1 );
      return tmp;
    }
    
    int thread_Renderer( void *data )
    {
      /* ToDo: Replace with Assert Statement */
      if( data == NULL ){ return -1; }
      data_t *mdata  = (data_t *)data;
      SDL_Window *window = Init_GL_Window( "Test Fenster", 480, 240 );
      mdata->win = window;
      SDL_GLContext glcontext = SDL_GL_CreateContext( window );
      if( glcontext == NULL )
      {
        logSDLError( "Could not create Context", __FUNCTION__ );
        return -2;
      }
      /*Start of Rendering Test*/
      glClearColor( *(mdata->r), *(mdata->g), *(mdata->b), 1.0 );
      glClear( GL_COLOR_BUFFER_BIT );
      SDL_GL_SwapWindow( window );
      while( mdata->running )
      {
        SDL_Delay( 250 );
        glClearColor( *(mdata->r), *(mdata->g), *(mdata->b), 1.0 );
        glClear( GL_COLOR_BUFFER_BIT );
        SDL_GL_SwapWindow( window );
      }
      SDL_DestroyWindow( window );
      SDL_GL_DeleteContext( glcontext );
      return 0;
    }
    
    int main( int argc, char **argv )
    {
      if( atexit( SDL_Quit ) && atexit( TTF_Quit ) )
      {
        return EXIT_FAILURE;
      }
      SDL_Event sevent;
      float r = 0.0;
      float g = 0.0;
      float b = 0.0;
      data_t main_data;
      main_data.event = &sevent;
      main_data.r = &r;
      main_data.g = &g;
      main_data.b = &b;
      main_data.running = true;
      SDL_Thread *thread_Ev = SDL_CreateThread( thread_Renderer, "rendererThread", &main_data );
      if( thread_Ev == NULL )
      {
        logSDLError( "Could not create Render Thread", __FUNCTION__ );
        exit( -3 );
      }
      bool loop = true;
      while( loop )
      {
        while( SDL_PollEvent( &sevent ) )
        {
          printf( SDL_GetError() );
          if( sevent.type == SDL_QUIT )
          {
            loop = false;
          }
          if( sevent.type == SDL_KEYUP )
          {
            printf( "Got Key: %c\n", sevent.key.keysym.sym );
            switch( sevent.key.keysym.sym )
            {
              case SDLK_ESCAPE:
                loop = false;
                main_data.running = false;
                break;
              case SDLK_r:
                *(main_data.r) += 0.01;
                printf( "Current R: %f\n", *(main_data.r) );
                break;
              case SDLK_g:
                *(main_data.g) += 0.01;
                printf( "Current G: %f\n", *(main_data.g) );
                break;
              case SDLK_b:
                *(main_data.b) += 0.01;
                printf( "Current B: %f\n", *(main_data.b) );
                break;
              default:
                printf( "Got Key: %c\n", sevent.key.keysym.sym );
                break;
            }
          }
        }
      }
      SDL_WaitThread( thread_Ev, NULL );
      TTF_Quit();
      SDL_Quit();
      return 0;
    }
    

  • Mod

    Responsiv in welchem Sinne? Responsiv ist normalerweise wenn der user auf seine Aktion, zeitnah, eine Antwort sehen kann. Inputs viel schneller zu verarbeiten als der Respond angezeigt wird, wird also in diesem Sinne nichts an Responsivitaet bringen.

    [quote]Sollte ich das überhaupt tun[/quote]Nein, du solltest dein Program nicht komplexer machen ohne einen gegenwärtigen Vorteil zu haben oder ein Problem zu loesen.

    Ja, manche OpenGL driver haben rumgesponnen wenn Rendern bzw Ausgabe nicht im main thread waren.



  • @rapso
    Danke dir für die Antwort rapso, habe jetzt erst wieder in den Thread hier geschaut.

    Ich habe dann in diesem Fall den Begriff Responsivität falsch verwendet. Mir ging es darum, dass ich trotz langer IO Vorgänge oder zähem Rendering dem Benutzer die Möglichkeit geben möchte, dass Spiel dennoch zu beenden, ohne dies auf einem anderen Weg forcieren zu müssen da die Oberfläche nicht reagiert. Und dies ist nur gegeben, wenn ich sicherstellen kann, dass bspw. bei SDL immer die Events abgefragt werden.

    Das war das Problem, was ich versucht habe zu umgehen/lösen wie auch immer man es bezeichnen möchte.

    Ja, manche OpenGL driver haben rumgesponnen wenn Rendern bzw Ausgabe nicht im main thread waren.
    

    Kann man das 'manche' denn eingrenzen? Handelte es sich hierbei um ältere Treiber oder betrifft dies auch die neueren? Die Frage natürlich unter Vorbehalt, dass da Erfahrungswerte bestehen.

    Für mich ist das ja dennoch interessant, da man in der Zukunft ja nicht um das arbeiten in mehreren Threads herum kommt meiner Ansicht nach.



  • @fairiestoy sagte in SDL2/OpenGL Threads:

    die Frage ist jedoch: Sollte ich das überhaupt tun?

    Die Frage lautet eher, warum glaubst du es tun zu wollen/müssen ?

    Den "renderthread" stösst du 30-60 mal pro sekunde an, wenn du da drinn performance probleme hasst, musst du eh kreativ werden. Ob man das dann als .... "den renderthread in einen anderen thread verlagern" oder "rechenintensive operationen" aus dem renderthread auslagern" bezeichnet, ist doch wurscht 🙂

    Ja, manche OpenGL driver haben rumgesponnen wenn Rendern bzw Ausgabe nicht im main thread waren.

    Ich hab mit reinem OGL 2.1 ned viel gemacht, aber moderneres OGL ist es eigentlich egal, ob die OGL funktionen von dem thread der "main" aus gecallt werden, oder von einem anderen. Wichtig ist nur das die "kritischen" calls aus dem selben Thread kommen.
    Was verwendest du fürs Fenstermanagement und was für den Input ? bzw wie funktioniert SDL da? Vielleicht liegt da das Problem? Wenn die SDL impliziet OGL calls macht, und den thread dazu als mainthread bezeichnet, bist natürlich drann gebunden. Wer macht die Swapchain, und wer das fensterhandling ?

    Ciao ...



  • @RHBaum

    @RHBaum sagte in SDL2/OpenGL Threads:

    Die Frage lautet eher, warum glaubst du es tun zu wollen/müssen ?

    Wie implizit angemerkt, halte ich es für sinnvoll mich mit Multithreading bereits auseinander zu setzen und ich empfand es als sinnvoll, den für das Rendern verantwortlichen Thread auszulagern, damit der Event Queue immer fleißig abgefragt wird. Damit soll verhindert werden, dass das OS zeitweise der Meinung ist, dass die Anwendung nicht mehr auf Eingaben reagiert. Das finde ich persönlich einfach eine hässliche Herangehensweise.

    @RHBaum sagte in SDL2/OpenGL Threads:

    Was verwendest du fürs Fenstermanagement und was für den Input ? bzw wie funktioniert SDL da? Vielleicht liegt da das Problem? Wenn die SDL impliziet OGL calls macht, und den thread dazu als mainthread bezeichnet, bist natürlich drann gebunden. Wer macht die Swapchain, und wer das fensterhandling ?

    Ich nutze SDL um das Fenster zu erstellen. SDL ist somit für das Windowhandling als auch die "Swapchain" zuständig (sofern du mit Swapchain diesen Codeteil meinst:

    SDL_GL_SwapWindow( window );
    

    Ich weiß jetzt nicht genau, auf was für ein Problem du dich beziehst um ehrlich zu sein ^^

    Interessanterweise, wie im obigen Code Beispiel zu sehen, kann ich das Eventhandling in einem separaten Thread ausführen, womit ich zunächst nicht gerechnet habe.



  • Interessanterweise, wie im obigen Code Beispiel zu sehen, kann ich das Eventhandling in einem separaten Thread ausführen

    und ich wollt nur ausdrücken, das es OpenGL eigentlich "egal" ist, welcher thread deine ogl-funktionen befeuert.

    dass es unsinnig sei und kein Performance Gewinn dabei heraus kommt, wenn man das Rendering auf Threads auslagert.

    was ich ähnlich sehe ....
    Allerding sehe ich den Gewinn zwischen "mainthread -> ogl" & "expliciter thread -> ogl" nicht als relevant an.
    Die opengl funktionen fressen kaum CPU performance und blockieren auch nicht, wenn der job auf der graka passiert.
    So das deine eventqueue eigentlich auch nie blockkiert werden sollte

    Damit soll verhindert werden, dass das OS zeitweise der Meinung ist, dass die Anwendung nicht mehr auf Eingaben >reagiert. Das finde ich persönlich einfach eine hässliche Herangehensweise.

    womit genau das verhindert werden sollte ....

    wenn es doch auftritt, hasst du irgendwas in der queue, was blockiert / oder cpu Last erzeugt, was eigentlich nicht OGL typisch ist

    Ich weiß jetzt nicht genau, auf was für ein Problem du dich beziehst um ehrlich zu sein

    dachte du hasst ein "reales" Problem mit blockierender eventqueue weswegen du dich mit dem, threading auseinander setzt