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.