Fix errors such as GL_FRAMEBUFFER_UNDEFINED and GL_INVALID_FRAMEBUFFER_OPERATION for OpenGL functions when the default framebuffer 0 is bound on macOS.
These errors happen because the NSOpenGLContext doesn't have a view bound at the time of an OpenGL call. The view could not be set for the NSOpenGLContext because the window or view can still be invisible. Setting an invisible view for the NSOpenGLContext can generate invalid drawable messages as 682ed910 has shown. Thus setting the view could be deferred because the NSOpenGLContext needs a visible view.
However, right after the window becomes visible, an OpenGL function that involves the default framebuffer can be called. And when the view is still not set at the time of the OpenGL call, errors such as GL_FRAMEBUFFER_UNDEFINED and GL_INVALID_FRAMEBUFFER_OPERATION could happen and result in rendering errors such as black screen. So we need to set the view for the NSOpenGLContext as soon as the view becomes visible.
It's possible that the window and view are still invisible when OpenGL functions involving the default framebuffer get called. In such cases, I think errors like GL_FRAMEBUFFER_UNDEFINED are justified on macOS.
Fix Active Trader Pro black screen at launch on macOS. The application creates a d3d9 device with an invisible window. And then it shows the window before calling d3d9_swapchain_Present(). So all the [NSOpenGLContext setView] opportunities are missed and no view is set. Then when glBlitFramebuffer() gets called, GL_FRAMEBUFFER_UNDEFINED happens and so nothing is rendered. The application only renders one frame before login so the window remains black.
From: Zhiyi Zhang zzhang@codeweavers.com
--- dlls/opengl32/tests/opengl.c | 92 ++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+)
diff --git a/dlls/opengl32/tests/opengl.c b/dlls/opengl32/tests/opengl.c index 74aff6b4bbd..7647ae6aa11 100644 --- a/dlls/opengl32/tests/opengl.c +++ b/dlls/opengl32/tests/opengl.c @@ -53,8 +53,29 @@ static void (WINAPI *pglDebugMessageCallbackARB)(void *, void *); static void (WINAPI *pglDebugMessageControlARB)(GLenum, GLenum, GLenum, GLsizei, const GLuint *, GLboolean); static void (WINAPI *pglDebugMessageInsertARB)(GLenum, GLenum, GLuint, GLenum, GLsizei, const char *);
+/* GL_ARB_framebuffer_object */ +static void (WINAPI *pglBindFramebuffer)(GLenum target, GLuint framebuffer); +static GLenum (WINAPI *pglCheckFramebufferStatus)(GLenum target); + static const char* wgl_extensions = NULL;
+static void flush_events(void) +{ + MSG msg; + int diff = 200; + int min_timeout = 100; + DWORD time = GetTickCount() + diff; + + while (diff > 0) + { + if (MsgWaitForMultipleObjects(0, NULL, FALSE, min_timeout, QS_ALLINPUT) == WAIT_TIMEOUT) + break; + while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) + DispatchMessageA(&msg); + diff = time - GetTickCount(); + } +} + static void init_functions(void) { #define GET_PROC(func) \ @@ -90,6 +111,10 @@ static void init_functions(void) GET_PROC(glDebugMessageControlARB) GET_PROC(glDebugMessageInsertARB)
+ /* GL_ARB_framebuffer_object */ + GET_PROC(glBindFramebuffer) + GET_PROC(glCheckFramebufferStatus) + #undef GET_PROC }
@@ -1335,6 +1360,72 @@ static void test_minimized(void) DestroyWindow(window); }
+static void test_framebuffer(void) +{ + static const PIXELFORMATDESCRIPTOR pf_desc = + { + sizeof(PIXELFORMATDESCRIPTOR), + 1, /* version */ + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, + 24, /* 24-bit color depth */ + 0, 0, 0, 0, 0, 0, /* color bits */ + 0, /* alpha buffer */ + 0, /* shift bit */ + 0, /* accumulation buffer */ + 0, 0, 0, 0, /* accum bits */ + 32, /* z-buffer */ + 0, /* stencil buffer */ + 0, /* auxiliary buffer */ + PFD_MAIN_PLANE, /* main layer */ + 0, /* reserved */ + 0, 0, 0 /* layer masks */ + }; + int pixel_format; + GLenum status; + HWND window; + HGLRC ctx; + BOOL ret; + HDC dc; + + /* Test the default framebuffer status for a window that becomes visible after wglMakeCurrent() */ + window = CreateWindowA("static", "opengl32_test", WS_POPUP, 0, 0, 640, 480, 0, 0, 0, 0); + ok(!!window, "Failed to create window, last error %#lx.\n", GetLastError()); + dc = GetDC(window); + ok(!!dc, "Failed to get DC.\n"); + pixel_format = ChoosePixelFormat(dc, &pf_desc); + if (!pixel_format) + { + win_skip("Failed to find pixel format.\n"); + ReleaseDC(window, dc); + DestroyWindow(window); + return; + } + + ret = SetPixelFormat(dc, pixel_format, &pf_desc); + ok(ret, "Failed to set pixel format, last error %#lx.\n", GetLastError()); + ctx = wglCreateContext(dc); + ok(!!ctx, "Failed to create GL context, last error %#lx.\n", GetLastError()); + ret = wglMakeCurrent(dc, ctx); + ok(ret, "Failed to make context current, last error %#lx.\n", GetLastError()); + + ShowWindow(window, SW_SHOW); + flush_events(); + + pglBindFramebuffer(GL_FRAMEBUFFER, 0); + + status = pglCheckFramebufferStatus(GL_FRAMEBUFFER); + todo_wine_if(status == GL_FRAMEBUFFER_UNDEFINED) /* macOS */ + ok(status == GL_FRAMEBUFFER_COMPLETE, "Expected %#x, got %#x.\n", GL_FRAMEBUFFER_COMPLETE, status); + + ret = wglMakeCurrent(NULL, NULL); + ok(ret, "Failed to clear current context, last error %#lx.\n", GetLastError()); + ret = wglDeleteContext(ctx); + ok(ret, "Failed to delete GL context, last error %#lx.\n", GetLastError()); + ReleaseDC(window, dc); + DestroyWindow(window); +} + static void test_window_dc(void) { PIXELFORMATDESCRIPTOR pf_desc = @@ -2162,6 +2253,7 @@ START_TEST(opengl) test_colorbits(hdc); test_gdi_dbuf(hdc); test_acceleration(hdc); + test_framebuffer();
wgl_extensions = pwglGetExtensionsStringARB(hdc); if(wgl_extensions == NULL) skip("Skipping opengl32 tests because this OpenGL implementation doesn't support WGL extensions!\n");
From: Zhiyi Zhang zzhang@codeweavers.com
Fix errors such as GL_FRAMEBUFFER_UNDEFINED and GL_INVALID_FRAMEBUFFER_OPERATION for OpenGL functions when the default framebuffer 0 is bound on macOS.
These errors happen because the NSOpenGLContext doesn't have a view bound at the time of an OpenGL call. The view could not be set for the NSOpenGLContext because the window or view can still be invisible. Setting an invisible view for the NSOpenGLContext can generate invalid drawable messages as 682ed910 has shown. Thus setting the view could be deferred because the NSOpenGLContext needs a visible view.
However, right after the window becomes visible, an OpenGL function that involves the default framebuffer can be called. And when the view is still not set at the time of the OpenGL call, errors such as GL_FRAMEBUFFER_UNDEFINED and GL_INVALID_FRAMEBUFFER_OPERATION could happen and result in rendering errors such as black screen. So we need to set the view for the NSOpenGLContext as soon as the view becomes visible.
It's possible that the window and view are still invisible when OpenGL functions involving the default framebuffer get called. In such cases, I think errors like GL_FRAMEBUFFER_UNDEFINED are justified on macOS.
Fix Active Trader Pro black screen at launch on macOS. The application creates a d3d9 device with an invisible window. And then it shows the window before calling d3d9_swapchain_Present(). So all the [NSOpenGLContext setView] opportunities are missed and no view is set. Then when glBlitFramebuffer() gets called, GL_FRAMEBUFFER_UNDEFINED happens and so nothing is rendered. The application only renders one frame before login so the window remains black. --- dlls/opengl32/tests/opengl.c | 1 - dlls/winemac.drv/cocoa_opengl.m | 7 ++++++- dlls/winemac.drv/cocoa_window.m | 1 + 3 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/dlls/opengl32/tests/opengl.c b/dlls/opengl32/tests/opengl.c index 7647ae6aa11..ebeefffb72c 100644 --- a/dlls/opengl32/tests/opengl.c +++ b/dlls/opengl32/tests/opengl.c @@ -1415,7 +1415,6 @@ static void test_framebuffer(void) pglBindFramebuffer(GL_FRAMEBUFFER, 0);
status = pglCheckFramebufferStatus(GL_FRAMEBUFFER); - todo_wine_if(status == GL_FRAMEBUFFER_UNDEFINED) /* macOS */ ok(status == GL_FRAMEBUFFER_COMPLETE, "Expected %#x, got %#x.\n", GL_FRAMEBUFFER_COMPLETE, status);
ret = wglMakeCurrent(NULL, NULL); diff --git a/dlls/winemac.drv/cocoa_opengl.m b/dlls/winemac.drv/cocoa_opengl.m index 31a1f28970b..fa69be7d927 100644 --- a/dlls/winemac.drv/cocoa_opengl.m +++ b/dlls/winemac.drv/cocoa_opengl.m @@ -83,7 +83,12 @@ - (void) resetSurfaceIfBackingSizeChanged macdrv_set_view_backing_size((macdrv_view)self.view, view_backing);
NSView* save = self.view; - OnMainThread(^{ + if ([NSThread isMainThread]) + { + [super clearDrawable]; + [super setView:save]; + } + else OnMainThread(^{ [super clearDrawable]; [super setView:save]; }); diff --git a/dlls/winemac.drv/cocoa_window.m b/dlls/winemac.drv/cocoa_window.m index 1655ea98ef7..9d2a53c60ae 100644 --- a/dlls/winemac.drv/cocoa_window.m +++ b/dlls/winemac.drv/cocoa_window.m @@ -570,6 +570,7 @@ - (void) viewWillDraw clearedGlSurface = TRUE; } context.needsUpdate = TRUE; + macdrv_update_opengl_context(context); } [glContexts addObjectsFromArray:pendingGlContexts]; [pendingGlContexts removeAllObjects];
I haven't tested it yet and I might be missing some macOS-specific quirk or similar, but in principle this looks good. Extra thanks for the test :smile: