2016-12-24 3 views
1

Я пытаюсь сделать OpenGL рендеринг в отдельном потоке на OSX. Я использую SDL для создания окна, но я хочу написать код создания контекста OpenGL вручную. Иногда он отлично работает (он должен показывать зеленый квадрат в красном поле), но иногда он просто отображает белый цвет.Состояние гонки в OSX OpenGL код установки

Если я запускаю только один поток (здесь есть #define ниже, чтобы включать и выключать), все работает нормально. Если я вставлю стойло (для цикла, насчитывающего 10 миллионов, другой переключатель #define для управления этим), он отлично работает, что заставляет меня полагать, что у меня есть условие гонки, и что мне нужно блокировать поток рендеринга, пока ОС не будет сделал то, что он сделал.

Не знаком с Какао или Цель-C, как это сделать? Или моя проблема что-то еще?

код следующим образом:

#include </Library/Frameworks/SDL2.framework/Headers/SDL.h> 
#include </Library/Frameworks/SDL2.framework/Headers/SDL_syswm.h> 
#include <OpenGL/GL3.h> 
#include <array> 
#import <Cocoa/Cocoa.h> 
#include <OpenGL/CGLTypes.h> 
#include <OpenGL/OpenGL.h> 
#include <OpenGL/CGLRenderers.h> 
#include <thread> 

namespace 
{ 
    float const PositionData[] = 
    { 
     -0.5f,-0.5f,0, 0,0, 
     0.5f,-0.5f,0, 0,0, 
     0.5f, 0.5f,0, 0,0, 
     0.5f, 0.5f,0, 0,0, 
     -0.5f, 0.5f,0, 0,0, 
     -0.5f,-0.5f,0, 0,0, 
    }; 

    namespace buffer 
    { 
     enum type 
     { 
      VERTEX, 
      TRANSFORM, 
      MATERIAL, 
      MAX 
     }; 
    }//namespace buffer 
}//namespace 

#define RENDER_THREAD 
#define BLOCK_RENDER_THREAD 

int main() { 
    Uint32 init_mode = SDL_INIT_VIDEO | SDL_INIT_TIMER; 

#ifdef _DEBUG 
    init_mode |= SDL_INIT_NOPARACHUTE; 
#endif 

    SDL_Init(init_mode); 

    Uint32 window_mode = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE; 

    SDL_Window* window; 

    if (NULL == (window = SDL_CreateWindow("Test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 500, 500, window_mode))) { 
     SDL_Quit(); 
     return 1; 
    } 

    SDL_SysWMinfo wmi; 
    SDL_VERSION(&wmi.version); 

    if (!SDL_GetWindowWMInfo(window, &wmi)) 
    { 
     return 1; 
    } 

    std::atomic<bool> closing(false); 

    auto PollEventQueue = [&closing]() { 
     SDL_Event e; 

     while (SDL_PollEvent(&e)) { 
      switch (e.type) 
      { 
      case SDL_QUIT: { 
       closing = true; 
      } break; 

      default: { 
      } break; 
      } 
     } 
    }; 

    NSWindow* native_window = wmi.info.cocoa.window; 

    auto RenderThreadMain = [native_window, &closing, PollEventQueue]() { 
#ifdef BLOCK_RENDER_THREAD 
     for (int k = 0; k < 10000000; k++) {} 
#endif 

     NSOpenGLContext* context; 
     @synchronized (native_window) { 
      NSOpenGLPixelFormat *pixel_format = nullptr; 

      NSOpenGLPixelFormatAttribute attributes[64] = { 
       NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion4_1Core, 
       NSOpenGLPFAColorSize, 24, 
       NSOpenGLPFAAlphaSize, 8, 
       NSOpenGLPFADepthSize, 24, 
       NSOpenGLPFAStencilSize, 8, 

       NSOpenGLPFADoubleBuffer, 
       NSOpenGLPFAAccelerated, 
       NSOpenGLPFANoRecovery, 

       0 
      }; 

      NSOpenGLPixelFormatAttribute* the_end = std::find_if(std::begin(attributes), std::end(attributes), [](NSOpenGLPixelFormatAttribute attribute) { 
       return attribute == 0; 
      }); 

      if (true) { 
       NSOpenGLPixelFormatAttribute multisample_attributes[] = { 
        NSOpenGLPFAMultisample, 
        NSOpenGLPFASampleBuffers, NSOpenGLPixelFormatAttribute(1), 
        NSOpenGLPFASamples, NSOpenGLPixelFormatAttribute(4), 
        0 
       }; 

       // Copy it onto the attributes array 
       int k = 0; 
       while (multisample_attributes[k]) { 
        *(the_end++) = multisample_attributes[k++]; 
       } 
      } 

      NSView* native_view = [native_window contentView]; 
      NSRect native_rect = [native_view bounds]; 

      pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; 
      NSOpenGLView* gl_view = [[NSOpenGLView alloc] initWithFrame:native_rect pixelFormat:pixel_format]; 
      [pixel_format release]; 

      [gl_view setAutoresizingMask: 
       (NSViewHeightSizable|NSViewWidthSizable|NSViewMinXMargin|NSViewMaxXMargin|NSViewMinYMargin|NSViewMaxYMargin) 
      ]; 
      [native_view addSubview:gl_view]; 

      context = [gl_view openGLContext]; 

      GLint swap_interval = 1; 
      [context setValues:&swap_interval forParameter:NSOpenGLCPSwapInterval]; 

      [context setView:[native_window contentView]]; 

      [context makeCurrentContext]; 
     } 

     std::array<GLuint, buffer::MAX> BufferName; 
     GLuint ProgramName; 
     GLuint VertexArrayName; 
     GLint UniformTransform; 
     GLint UniformMaterial; 

     const char* vertex_shader = 
      "#version 150 core\n" 
      "in vec3 Position;" 
      "in vec2 UV;" 
      "void main()" 
      "{" 
      " gl_Position = vec4(Position, 1.0);" 
      "}"; 
     const GLint vertex_shader_length = (GLint)strlen(vertex_shader); 

     const char* fragment_shader = 
      "#version 150 core\n" 
      "out vec4 Color;" 
      "void main()" 
      "{" 
      " Color = vec4(0.0, 1.0, 0.0, 1.0);" 
      "}"; 
     const GLint fragment_shader_length = (GLint)strlen(fragment_shader); 

     GLuint fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER); 
     glShaderSource((GLuint)fragment_shader_id, 1, &fragment_shader, &fragment_shader_length); 
     glCompileShader((GLuint)fragment_shader_id); 

     int shader_compiled; 
     glGetShaderiv((GLuint)fragment_shader_id, GL_COMPILE_STATUS, &shader_compiled); 
     if (shader_compiled != GL_TRUE) { 
      int log_length = 0; 
      char log[1024]; 
      glGetShaderInfoLog((GLuint)fragment_shader_id, 1024, &log_length, log); 
      printf("%s", log); 
      return 1; // TODO: Error 
     } 

     GLuint vertex_shader_id = glCreateShader(GL_VERTEX_SHADER); 
     glShaderSource((GLuint)vertex_shader_id, 1, &vertex_shader, &vertex_shader_length); 
     glCompileShader((GLuint)vertex_shader_id); 

     glGetShaderiv((GLuint)vertex_shader_id, GL_COMPILE_STATUS, &shader_compiled); 
     if (shader_compiled != GL_TRUE) { 
      int log_length = 0; 
      char log[1024]; 
      glGetShaderInfoLog((GLuint)vertex_shader_id, 1024, &log_length, log); 
      printf("%s", log); 
      return 1; // TODO: Error 
     } 

     ProgramName = glCreateProgram(); 
     glAttachShader(ProgramName, fragment_shader_id); 
     glAttachShader(ProgramName, vertex_shader_id); 

     glBindAttribLocation(ProgramName, 0, "Position"); 
     glLinkProgram(ProgramName); 

     glGenBuffers(buffer::MAX, &BufferName[0]); 

     glBindBuffer(GL_ARRAY_BUFFER, BufferName[buffer::VERTEX]); 
     glBufferData(GL_ARRAY_BUFFER, sizeof(PositionData), PositionData, GL_STATIC_DRAW); 
     glBindBuffer(GL_ARRAY_BUFFER, 0); 

     glGenVertexArrays(1, &VertexArrayName); 
     glBindVertexArray(VertexArrayName); 
      glBindBuffer(GL_ARRAY_BUFFER, BufferName[buffer::VERTEX]); 
      glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), 0); 
      glVertexAttribPointer(4, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (const GLvoid *)(3*sizeof(float))); 

      glEnableVertexAttribArray(0); 
      glEnableVertexAttribArray(4); 
     glBindVertexArray(0); 

     while (!closing) { 
#ifndef RENDER_THREAD 
      PollEventQueue(); 
#endif 

      glClearColor(1, 0, 0, 1); 
      glClear(GL_COLOR_BUFFER_BIT); 

      glUseProgram(ProgramName); 
      glBindVertexArray(VertexArrayName); 
      glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 2); 

      [context flushBuffer]; 
      [context update]; 
     } 

     return 0; 
    }; 

#ifdef RENDER_THREAD 
    std::thread render_thread = std::thread(RenderThreadMain); 

    while (!closing) { 
     PollEventQueue(); 
    } 

    render_thread.join(); 
#else 
    RenderThreadMain(); 
#endif 

    return 0; 
} 

Собран с:

clang++ test.mm -framework OpenGL -framework Cocoa -framework SDL2 -F/Library/Frameworks -std=c++14 -g 

ответ

2

Во-первых, вы должны ознакомиться с Apple, Thread Safety Summary для какао (особенно AppKit). Там вы узнаете, что не следует манипулировать иерархией представлений из вторичного потока. В частности, звонок -addSubview: является плохим. (Обратите внимание, что ограничения, касающиеся чертежей из фоновых потоков, описанных там, применимы только к обычным чертежам. OpenGL не требует блокировки фокуса на представлении.)

Использование @synchronized(native_window) не делает то, что вы думаете. Он только синхронизируется с другим кодом, который также явно использует @synchronized() на том же самом объекте. Он не обычно синхронизируется с чем угодно, просто использует или работает в этом окне. У меня нет оснований полагать, что что-либо в Cocoa делает @synchronized() на своих окнах, поэтому вы синхронизированы ни с чем.

Поскольку настройка окна и контекста является разовой работой, ее следует, вероятно, просто сделать на основном потоке до того, как будет создан вторичный поток.

Во-вторых, вы создаете NSOpenGLView и получаете контекст от этого, но затем вы говорите контексту, чтобы связать себя с другим видом (contentView) окна. Зачем ты это делаешь? NSOpenGLView владеет этим контекстом, и он должен, вероятно, оставаться связанным с этим представлением.

И наконец, используемая вами маска для автоматического изменения размеров кажется странной. Поскольку вы позволяете всем быть гибкими, просмотр GL не будет синхронизироваться с представлением содержимого окна. Он будет расти и сокращаться медленнее, при этом поля вокруг него набирают и теряют слабину, как это происходит. Я предполагаю, что вы просто хотите NSViewHeightSizable|NSViewWidthSizable, чтобы размеры отображались вместе, а поля оставались 0.

+0

«Почему вы это делаете?» Потому что я понятия не имею, что я делаю: P Ваши комментарии очень ценятся. –

+0

Так что я должен: 1. Do GL инициализации контекста от основного потока 2. Избавьтесь от @synchronized 3. Создание и пусть умирает один NSThread так, что какао знает, что я делаю многопоточных вещи и может создавать мьютексы 4. Создайте новый контекст GL в потоке рендеринга, используя параметр share: parameter 5. Очистите свой материал View и мои файлы для автоматического изменения размера –

+0

Хм, на самом деле, похоже, у меня больше успеха, создавая только один контекст в основной теме, а затем выполнив [context makeCurrentContext] в потоке рендеринга. –

Смежные вопросы