【问题标题】:Race condition in OSX OpenGL setup codeOSX OpenGL 设置代码中的竞争条件
【发布时间】:2017-05-10 00:51:22
【问题描述】:

我正在尝试在 OSX 上的单独线程中进行 OpenGL 渲染。我正在使用 SDL 创建窗口,但我想手动编写 OpenGL 上下文创建代码。有时它可以正常工作(它应该在红色区域中显示一个绿色方块),但有时它只显示白色。

如果我只在一个线程中运行(下面有一个#define 来打开和关闭它)一切正常。如果我插入一个停顿(一个 for 循环计数到 1000 万,另一个 #define 开关来控制它)它工作正常,这让我相信我有一个竞争条件,并且我需要阻塞渲染线程直到操作系统做任何事情。

不熟悉 Cocoa 或 Objective-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

【问题讨论】:

    标签: c++ macos cocoa opengl sdl


    【解决方案1】:

    首先,您应该熟悉 Apple 针对 Cocoa(尤其是 AppKit)的 Thread Safety Summary。在那里,您将了解到您不应该从辅助线程操作视图层次结构。特别是对-addSubview: 的调用很糟糕。 (请注意,此处描述的从后台线程绘制的限制仅适用于常规绘制。OpenGL 不要求您将焦点锁定在视图上。)

    使用@synchronized(native_window) 并不符合您的想法。它仅与在同一对象上显式使用@synchronized() 的其他代码同步。它通常与仅在该窗口上使用或操作的任何内容同步。我没有理由相信 Cocoa 中的任何东西都会在它的窗口上执行@synchronized(),所以你什么都没有同步。

    由于窗口和上下文的设置是一次性工作,因此可能应该在生成辅助线程之前在主线程上完成。

    其次,您正在创建一个NSOpenGLView 并从中获取上下文,但随后您要告诉上下文将自己与不同的视图(窗口的contentView)相关联。你为什么这样做? NSOpenGLView 拥有该上下文,它可能应该与该视图保持关联。

    最后,您使用的自动调整大小的蒙版看起来很奇怪。由于您允许一切灵活,GL 视图不会与窗口的内容视图保持同步。它的增长和收缩会更慢,它周围的利润会随着它的变化而增加和减少。我猜你只是想要NSViewHeightSizable|NSViewWidthSizable 以便视图一起调整大小并且边距保持为0。

    【讨论】:

    • “你为什么要这么做?”因为我不知道自己在做什么:P 非常感谢您的 cmets。
    • 所以我应该: 1. 从主线程执行 GL 上下文初始化 2. 摆脱 @synchronized 3. 创建并让单个 NSThread 死掉,以便 Cocoa 知道我正在做多线程的事情并且可以创建互斥锁 4. 在渲染线程中创建一个新的 GL 上下文,使用 share: 参数 5. 清理我的视图内容和我的自动调整大小的内容
    • 嗯,实际上我似乎更成功地在主线程上只创建一个上下文,然后在渲染线程上执行 [context makeCurrentContext]。
    • 1.是的。 2 是的。 3. 这些天不应该是必要的,但我想不会受伤。如果您担心,我想您可以查看[NSThread isMultiThreaded]。 4. 正如你所发现的,不应该是必要的。 5. 是的,具体取决于您的意思。 ;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-16
    • 1970-01-01
    • 2014-02-21
    相关资源
    最近更新 更多