Page MenuHome

MacOS OpenGL error in initializing offscreen context
Open, NormalPublic

Description

Hi.

The first time when an offscreen OpenGL context is created with GHOST_ContextCGL::createOffscreenContext(), glClear(GL_COLOR_BUFFER_BIT) is called while glCheckFramebufferStatus(GL_FRAMEBUFFER) is GL_FRAMEBUFFER_UNDEFINED, resulting in an glError in GHOST_Context::initClearGL().

When a new window with an (onscreen) OpenGLContext is created, initClearGL() is OK, because makeCurrentContext did set the gl-context.

void GHOST_Context::initClearGL()
{
    //
    // Add this to confirm the error:
    //
    GLint status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE) {
        printf("Framebuffer not complete!\n");
        printf("OpenGL error: 0x%x\n", status);        
    }
    
    glClearColor(0.447, 0.447, 0.447, 0.000);
    glClear(GL_COLOR_BUFFER_BIT);
    glClearColor(0.000, 0.000, 0.000, 0.000);
}

Some possible (although maybe incorrect) fixes:

  1. Only call initClearGL() when a window is created.
//
// This function is called for both on- and offscreen gl context
//
GHOST_ContextCGL::initializeDrawingContext() {
    // ...

    // if (m_openGLView) is the check for on- or offscreen

    // version 1a
    if (m_openGLView) {
        initClearGL();
    }
    [m_openGLContext flushBuffer];
	    
    // or this?
	    
    // version 1b
    if (m_openGLView) {
        initClearGL();
        [m_openGLContext flushBuffer];
    }

    // ...
}
  1. I think a better solution would be to move the gl-commands (initClearGL and flushBuffer) to another part in the initialization, after GHOSTContextCGL::activateDrawingContext() is called, so every operating system has the same initialization. This requires more code-refactoring, creating possible bugs at other operating systems, and I can only test MacOS.

    Removing both initClearGL() and [m_openGLContext flushBuffer] from GHOST_ContextCGL::initializeDrawingContext() in a simple test didn't show any errors (for both on- and offscreen gl-contexts) and it looks like everything is still working OK.

I think fix #1 is simple, solves at least a single glError and doesn't break anything. Not sure if [m_openGLContext flushBuffer] should also be disabled when creating an offscreen gl-context, but the flushing would be useless when nothing is done on this gl-context, right?

Both possible fixes in #1 (version 1a or 1b) don't generate a glError.

Also fix #2 (remove both functions for on- and offscreen context) don't generate a glError.

OS: MacOS Mojave (10.14.1)
GPU: AMD Radeon RX 560

Details

Type
OpenGL Error

Event Timeline

Brecht Van Lommel (brecht) triaged this task as Normal priority.Fri, Nov 30, 11:37 AM
Brecht Van Lommel (brecht) claimed this task.

Hi. I looked at it again and it might be more problematic.

The class NSOpenGLView/NSOpenGLContext is doing more gl-commands that keeps breaking it. With the help of apitrace I found out that it is somehow keeping a wrong GLPixelFormat cached somewhere.

216 @0 CGLChoosePixelFormat(attribs = [kCGLPFAAccelerated, kCGLPFANoRecovery, kCGLPFAAllowOfflineRenderers, 1262, kCGLPFASingleRenderer, kCGLPFADisplayMask, 2, 0], pix = [0x7f86abdb6240], npix = [1]) = kCGLNoError
unexpected attribute 1262

Not only is the unexpected attribute a problem, the other values were never defined anywhere in Blender so it is done by OSX.

When [m_openGLContext flushBuffer] is called, it internally calls CGLChoosePixelFormat().

After disabling flushBuffer, the same would happen when swapBuffers() is called.

On the more recent OSX version, all views are layer-backed views. (Also the end of subpixel antialiasing for fonts, which is noticeable on non-retina screens).

All NSView-classes automatically use the CALayer for drawing and NSView would handle the rest (Events etc.). So in reality, everything is offscreen-rendering and OSX can apply their own effects (like the bottle-minimizing-window-effect).

My suggestion would be to remove NSOpenGLView and NSOpenGLContext completely, since Apple gave tips in 2011 on how to port your code from NSOpenGLContext to CGLContextObj.

(the old disabled, and new code in a single file)
https://github.com/ewmailing/GLEssentialsPortToCAOpenGLLayer/blob/master/Classes/Mac/NSGLView.m

Currently in Blender it is CocoaOpenGLView -> NSOpenGLView -> NSView.

The new way would be CocoaOpenGLView -> NSView, where CocoaOpenGLView would still handle all events exactly as it is right now. But the NSView's default layer CALayer has to be a CAOpenGLLayer.

I have done some tests without success, but I think this is the way to go and keep trying.


To use apitrace MacOS 10.14.1 right now:

https://github.com/apitrace/apitrace/issues/594#issuecomment-439684770

The workaround I recommend in the meanwhile is to use two checkouts of apitrace:

https://github.com/apitrace/apitrace/tree/dyld-interpose for tracing

https://github.com/apitrace/apitrace/tree/master for replaying

./apitrace trace /[PATH]/blender/_build/bin/Debug/blender.app/Contents/MacOS/blender


xCode tip:

Add a symbolic breakpoint in xCode like for example the CGLChoosePixelFormat. It also does a break when it was called somewhere internally, like in [m_openGLContext flushBuffer].

Hi. I tried to create a "patch.diff", but it didn't work out very well (I'm not familiar with patching yet), so I will just upload 2 files in this post.

It contains changes in 2 files which won't affect the current build (AFAIK), but would be a start of the transition to remove NSOpenGLView/NSOpenGLContext.


diff #1: blender/build_files/cmake/platform/platform_apple.cmake

only add: -framework OpenGL

set(PLATFORM_LINKFLAGS
	"-fexceptions -framework CoreServices -framework Foundation -framework IOKit -framework AppKit -framework Cocoa -framework Carbon -framework AudioUnit -framework AudioToolbox -framework CoreAudio -framework OpenGL"
)

diff #2: blender/intern/ghost/intern/GHOST_ContextCGL.mm

change:

static void makeAttribList(std::vector<NSOpenGLPixelFormatAttribute>& attribs, ...)

to

static void makeAttribList(std::vector<CGLPixelFormatAttribute>& attribs, ...)

and some changes to keep using the NSGLOpenGLContext as it is right now.

(I probably should have added CGLReleasePixelFormat() or CGLDestroyPixelFormat(), but that isn't a big issue for now and it isn't documented very well either and those mem-leaks would be minimal, but must be noted. Releasing too much would definitely break things, and I don't know the retain cycle in this case.)


I would like to submit some bigger code-changes (as patches) in the future with the use of CAOpenGLLayer, so I was wondering if any developers are actively working on this part of MacOS?

The code will be more C++ and less Objective-C++ with finer control over OpenGL calls and less fuzziness of MacOS's internal OpenGL handling.

Hi. Sorry for kind of spamming this topic.

The changes I'm making now will be a lot less than I previously thought were needed (hopefully).

CocoaOpenGLView will stay a subclass of NSOpenGLView.

The changes will mostly be removing NSOpenGLContext in favor of CGLContext.

(Less Objective-C++ and less of Apple's "magic")

Makes sense, thanks for looking into this.

Hi. Here's a patch solves the current issue with the FRAMEBUFFER_UNDEFINED as mentioned in the opening post.

The error was because no glViewport() was set before glClear(GL_COLOR_BUFFER_BIT) in ::initClearGL() was called.

When an on-screen context is created, the [m_openGLView setOpenGLContext:] will internally do glViewport (and also glScissor).

NSOpenGLContext *prev_openGLContext is changed because it is needed for off-screen contexts.
(behavior is now more like to prevHGLRC = ::wglGetCurrentContext(); in GHOST_ContextWGL.cpp)

diff --git a/intern/ghost/intern/GHOST_ContextCGL.mm b/intern/ghost/intern/GHOST_ContextCGL.mm
index d95eba339f7..21b2cb6ed88 100644
--- a/intern/ghost/intern/GHOST_ContextCGL.mm
+++ b/intern/ghost/intern/GHOST_ContextCGL.mm
@@ -273,7 +273,7 @@ static void getVersion(int *major, int *minor)
 	std::vector<NSOpenGLPixelFormatAttribute> attribs;
 	attribs.reserve(40);
 
-	NSOpenGLContext *prev_openGLContext = (m_openGLView) ? [m_openGLView openGLContext] : NULL;
+	NSOpenGLContext *prev_openGLContext = [NSOpenGLContext currentContext];
 
 #ifdef GHOST_OPENGL_ALPHA
 	static const bool needAlpha   = true;
@@ -379,6 +379,13 @@ static void getVersion(int *major, int *minor)
 	if (m_openGLView) {
 		[m_openGLView setOpenGLContext:m_openGLContext];
 		[m_openGLContext setView:m_openGLView];
+	} else {
+		// Offscreen context
+		assert([prev_openGLContext view]);
+		NSRect viewFrame = [[prev_openGLContext view] frame];
+		GLsizei frameWidth = viewFrame.size.width;
+		GLsizei frameHeight = viewFrame.size.height;
+		glViewport(0, 0, frameWidth, frameHeight);
 	}
 
 	if (s_sharedCount == 0)
NOTE: Only tested on non-retina screen.

This crashes in background mode for me, running blender -b -E BLENDER_EEVEE -f 1.

Maybe clearing is not needed at all for an offscreen context though. We do that to show something in the window before the Blender UI is fully loaded, but for an offscreen context it does not matter.

In the error case at the end of the function, it's also restoring prev_openGLContext in m_openGLView, but that's no longer correct if that's not what we're getting it from anymore. As far as I can tell the only place where goto error happens is when we haven't modified the view though, so perhaps the code can be removed.

Hi. I will upload a new patch later today (code cleanup and without the previous "fix").

I will also take a better look at your post and code and get back to you!

It's crashing here too. I'll take a closer look at it this weekend.

I'm putting the cleanup-patch here for a quick look, but it still needs some checking/revisions.

I also should check out 'virtual screens' because of: [pixelFormat getValues:&actualSamples forAttribute:NSOpenGLPFASamples forVirtualScreen:0];

Some definitions are difficult to read: (snippets from docs).

The combination of renderer and physical display is called a virtual screen.
...
A simple system, with one graphics card and one physical display, typically has two virtual screens.

^^^ And this is just with 1 GPU and 1 Monitor. (next is multiple GPUs/monitors/screen-mirroring and hot-plugging eGPUs/monitors and GPU-switching, very complex)

relevant CGLPixelFormatAttributes: kCGLPFAVirtualScreenCount, kCGLPFARendererID, kCGLPFADisplayMask, kCGLPFASupportsAutomaticGraphicsSwitching.


Maybe a crazy idea now: Apple promotes their own Metal framework, but this can be mixed with OpenGL. Maybe the onscreen-buffer can be pushed into the Metal buffer and let them handle the rest. Lots of 'maybes', but maybe?


Niels (nielspl) added a comment.EditedSat, Dec 8, 4:01 PM

A little debugging update:

NSOpenGLContext *prev_openGLContext = (m_openGLView) ? [m_openGLView openGLContext] : NULL;

Left-side image: with prev_openGLContext
Right-side image: without prev_openGLContext

It looks like it creates a new (unwanted and "unused") context. And also 2 unwanted. PixelFormats,

Since it's the first (on screen) context creation:

NSOpenGLContext *prev_openGLContext = (s_sharedCount > 0 && m_openGLView) ? [m_openGLView openGLContext] : NULL;

Just for reference:

glPushGroupMarkerEXT(0, "[m_openGLView setOpenGLContext:m_openGLContext]");
[m_openGLView setOpenGLContext:m_openGLContext];
glPopGroupMarkerEXT();

glPushGroupMarkerEXT(0, "[m_openGLContext flushBuffer]");
[m_openGLContext flushBuffer];
glPopGroupMarkerEXT();