libobs-opengl: OpenGL thread-safety on Mac

Attempt to fix threading issues that cause OBS to force-crash when
compiled with latest Xcode. There are two places where the new SDK
introduces a force-crash because operations are not happening on the
main thread: when we modify the context view to switch swap chains, and
when we resize a swap chain.

Instead of using just one context for all rendering, we create an
additional context for each swap chain, set each view once on
initialization, and switch contexts only in present to blit the final
framebuffer. This is an extra copy, but it's pretty hairy to optimize
away, and it's not worth potential regressions just to speed up Mac.

For resizing, we schedule the update code to run on the main thread from
the render thread. Ideally, we wouldn't have to round trip the logic
from main thread to graphics thread and back, but I don't think we want
to hack up the interface for this, especially since OpenGL will give way
to Metal soon enough.
master
jpark37 2019-12-25 22:25:38 -08:00
parent 213c715f3d
commit 9f330050ef
6 changed files with 189 additions and 35 deletions

View File

@ -25,13 +25,16 @@
struct gl_windowinfo {
NSView *view;
NSOpenGLContext *context;
gs_texture_t *texture;
GLuint fbo;
};
struct gl_platform {
NSOpenGLContext *context;
};
static NSOpenGLContext *gl_context_create(void)
static NSOpenGLContext *gl_context_create(NSOpenGLContext *share)
{
unsigned attrib_count = 0;
@ -62,7 +65,8 @@ static NSOpenGLContext *gl_context_create(void)
}
NSOpenGLContext *context;
context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil];
context = [[NSOpenGLContext alloc] initWithFormat:pf
shareContext:share];
[pf release];
if (!context) {
blog(LOG_ERROR, "Failed to create context");
@ -76,28 +80,30 @@ static NSOpenGLContext *gl_context_create(void)
struct gl_platform *gl_platform_create(gs_device_t *device, uint32_t adapter)
{
struct gl_platform *plat = bzalloc(sizeof(struct gl_platform));
GLint interval = 0;
plat->context = gl_context_create();
if (!plat->context)
goto fail;
[plat->context makeCurrentContext];
[plat->context setValues:&interval forParameter:NSOpenGLCPSwapInterval];
if (!gladLoadGL())
goto fail;
return plat;
fail:
blog(LOG_ERROR, "gl_platform_create failed");
gl_platform_destroy(plat);
UNUSED_PARAMETER(device);
UNUSED_PARAMETER(adapter);
return NULL;
NSOpenGLContext *context = gl_context_create(nil);
if (!context) {
blog(LOG_ERROR, "gl_context_create failed");
return NULL;
}
[context makeCurrentContext];
GLint interval = 0;
[context setValues:&interval forParameter:NSOpenGLCPSwapInterval];
const bool success = gladLoadGL() != 0;
[NSOpenGLContext clearCurrentContext];
if (!success) {
blog(LOG_ERROR, "gladLoadGL failed");
[context release];
return NULL;
}
struct gl_platform *plat = bzalloc(sizeof(struct gl_platform));
plat->context = context;
return plat;
}
void gl_platform_destroy(struct gl_platform *platform)
@ -113,14 +119,72 @@ void gl_platform_destroy(struct gl_platform *platform)
bool gl_platform_init_swapchain(struct gs_swap_chain *swap)
{
UNUSED_PARAMETER(swap);
NSOpenGLContext *parent = swap->device->plat->context;
NSOpenGLContext *context = gl_context_create(parent);
bool success = context != nil;
if (success) {
CGLContextObj parent_obj = [parent CGLContextObj];
CGLLockContext(parent_obj);
return true;
[parent makeCurrentContext];
struct gs_init_data *init_data = &swap->info;
swap->wi->texture = device_texture_create(
swap->device, init_data->cx, init_data->cy,
init_data->format, 1, NULL, GS_RENDER_TARGET);
glFlush();
[NSOpenGLContext clearCurrentContext];
CGLContextObj context_obj = [context CGLContextObj];
CGLLockContext(context_obj);
[context makeCurrentContext];
[context setView:swap->wi->view];
GLint interval = 0;
[context setValues:&interval
forParameter:NSOpenGLCPSwapInterval];
gl_gen_framebuffers(1, &swap->wi->fbo);
gl_bind_framebuffer(GL_FRAMEBUFFER, swap->wi->fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
swap->wi->texture->texture, 0);
gl_success("glFrameBufferTexture2D");
glFlush();
[NSOpenGLContext clearCurrentContext];
CGLUnlockContext(context_obj);
CGLUnlockContext(parent_obj);
swap->wi->context = context;
}
return success;
}
void gl_platform_cleanup_swapchain(struct gs_swap_chain *swap)
{
UNUSED_PARAMETER(swap);
NSOpenGLContext *parent = swap->device->plat->context;
CGLContextObj parent_obj = [parent CGLContextObj];
CGLLockContext(parent_obj);
NSOpenGLContext *context = swap->wi->context;
CGLContextObj context_obj = [context CGLContextObj];
CGLLockContext(context_obj);
[context makeCurrentContext];
gl_delete_framebuffers(1, &swap->wi->fbo);
glFlush();
[NSOpenGLContext clearCurrentContext];
CGLUnlockContext(context_obj);
[parent makeCurrentContext];
gs_texture_destroy(swap->wi->texture);
glFlush();
[NSOpenGLContext clearCurrentContext];
swap->wi->context = nil;
CGLUnlockContext(parent_obj);
}
struct gl_windowinfo *gl_windowinfo_create(const struct gs_init_data *info)
@ -150,19 +214,62 @@ void gl_windowinfo_destroy(struct gl_windowinfo *wi)
void gl_update(gs_device_t *device)
{
[device->plat->context update];
gs_swapchain_t *swap = device->cur_swap;
NSOpenGLContext *parent = device->plat->context;
NSOpenGLContext *context = swap->wi->context;
dispatch_async(dispatch_get_main_queue(), ^() {
CGLContextObj parent_obj = [parent CGLContextObj];
CGLLockContext(parent_obj);
CGLContextObj context_obj = [context CGLContextObj];
CGLLockContext(context_obj);
[context makeCurrentContext];
[context update];
struct gs_init_data *info = &swap->info;
gs_texture_t *previous = swap->wi->texture;
swap->wi->texture = device_texture_create(device, info->cx,
info->cy,
info->format, 1, NULL,
GS_RENDER_TARGET);
gl_bind_framebuffer(GL_FRAMEBUFFER, swap->wi->fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
swap->wi->texture->texture, 0);
gl_success("glFrameBufferTexture2D");
gs_texture_destroy(previous);
glFlush();
[NSOpenGLContext clearCurrentContext];
CGLUnlockContext(context_obj);
CGLUnlockContext(parent_obj);
});
}
void gl_clear_context(gs_device_t *device)
{
UNUSED_PARAMETER(device);
[NSOpenGLContext clearCurrentContext];
}
void device_enter_context(gs_device_t *device)
{
CGLLockContext([device->plat->context CGLContextObj]);
[device->plat->context makeCurrentContext];
}
void device_leave_context(gs_device_t *device)
{
UNUSED_PARAMETER(device);
glFlush();
[NSOpenGLContext clearCurrentContext];
device->cur_render_target = NULL;
device->cur_zstencil_buffer = NULL;
device->cur_swap = NULL;
device->cur_fbo = NULL;
CGLUnlockContext([device->plat->context CGLContextObj]);
}
void *device_get_device_obj(gs_device_t *device)
@ -177,15 +284,35 @@ void device_load_swapchain(gs_device_t *device, gs_swapchain_t *swap)
device->cur_swap = swap;
if (swap) {
[device->plat->context setView:swap->wi->view];
} else {
[device->plat->context clearDrawable];
device_set_render_target(device, swap->wi->texture, NULL);
}
}
void device_present(gs_device_t *device)
{
[device->plat->context flushBuffer];
glFlush();
[NSOpenGLContext clearCurrentContext];
CGLUnlockContext([device->plat->context CGLContextObj]);
CGLLockContext([device->cur_swap->wi->context CGLContextObj]);
[device->cur_swap->wi->context makeCurrentContext];
gl_bind_framebuffer(GL_READ_FRAMEBUFFER, device->cur_swap->wi->fbo);
gl_bind_framebuffer(GL_DRAW_FRAMEBUFFER, 0);
const uint32_t width = device->cur_swap->info.cx;
const uint32_t height = device->cur_swap->info.cy;
glBlitFramebuffer(0, 0, width, height, 0, height, width, 0,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
[device->cur_swap->wi->context flushBuffer];
glFlush();
[NSOpenGLContext clearCurrentContext];
CGLUnlockContext([device->cur_swap->wi->context CGLContextObj]);
CGLLockContext([device->plat->context CGLContextObj]);
[device->plat->context makeCurrentContext];
}
void gl_getclientsize(const struct gs_swap_chain *swap, uint32_t *width,

View File

@ -149,12 +149,24 @@ static inline bool gl_bind_renderbuffer(GLenum target, GLuint buffer)
return gl_success("glBindRendebuffer");
}
static inline bool gl_gen_framebuffers(GLsizei num_arrays, GLuint *arrays)
{
glGenFramebuffers(num_arrays, arrays);
return gl_success("glGenFramebuffers");
}
static inline bool gl_bind_framebuffer(GLenum target, GLuint buffer)
{
glBindFramebuffer(target, buffer);
return gl_success("glBindFramebuffer");
}
static inline void gl_delete_framebuffers(GLsizei num_arrays, GLuint *arrays)
{
glDeleteFramebuffers(num_arrays, arrays);
gl_success("glDeleteFramebuffers");
}
static inline bool gl_tex_param_f(GLenum target, GLenum param, GLfloat val)
{
glTexParameterf(target, param, val);

View File

@ -245,7 +245,7 @@ int device_create(gs_device_t **p_device, uint32_t adapter)
gl_enable(GL_CULL_FACE);
gl_gen_vertex_arrays(1, &device->empty_vao);
device_leave_context(device);
gl_clear_context(device);
device->cur_swap = NULL;
#ifdef _WIN32

View File

@ -617,6 +617,7 @@ extern struct fbo_info *get_fbo(gs_texture_t *tex, uint32_t width,
uint32_t height);
extern void gl_update(gs_device_t *device);
extern void gl_clear_context(gs_device_t *device);
extern struct gl_platform *gl_platform_create(gs_device_t *device,
uint32_t adapter);

View File

@ -415,6 +415,12 @@ void gl_update(gs_device_t *device)
UNUSED_PARAMETER(device);
}
void gl_clear_context(gs_device_t *device)
{
UNUSED_PARAMETER(device);
wglMakeCurrent(NULL, NULL);
}
static void init_dummy_swap_info(struct gs_init_data *info)
{
info->format = GS_RGBA;
@ -541,8 +547,8 @@ void device_enter_context(gs_device_t *device)
void device_leave_context(gs_device_t *device)
{
wglMakeCurrent(NULL, NULL);
UNUSED_PARAMETER(device);
wglMakeCurrent(NULL, NULL);
}
void *device_get_device_obj(gs_device_t *device)

View File

@ -230,7 +230,6 @@ gl_windowinfo_create(const struct gs_init_data *info)
extern void gl_windowinfo_destroy(struct gl_windowinfo *info)
{
UNUSED_PARAMETER(info);
bfree(info);
}
@ -485,6 +484,15 @@ extern void gl_getclientsize(const struct gs_swap_chain *swap, uint32_t *width,
free(geometry);
}
extern void gl_clear_context(gs_device_t *device)
{
Display *display = device->plat->display;
if (!glXMakeContextCurrent(display, None, None, NULL)) {
blog(LOG_ERROR, "Failed to reset current context.");
}
}
extern void gl_update(gs_device_t *device)
{
Display *display = device->plat->display;