OSX OpenGL frame rate issues that aren't frame rate issues

On current SDL2 compiled from source code, on OSX only, I’m running into a weird problem, possibly only in OSX Mojave (I don’t have a non-Mojave OSX machine to test on, and Windows and Linux builds are fine)

What I’m seeing looks for all the world like a simple low frame rate; as if my game was running at perhaps 5-10fps. But my frame timings show that I’m running at almost 60fps on my test machine; it’s just that most of those frames aren’t making it to the screen? If I glReadPixels() back the pixels, I can capture frames that were never visibly displayed on the window.

It could well be a problem in my code. I haven’t yet set up a minimal test case to try to track down the issue, and that’s probably my next step. But I figured I’d ask around first, to see if anybody had any immediate ideas about issues I might be hitting.

The big unusual thing in my codebase: I have two shared OpenGL contexts. One is used for rendering, the other is used for loading data into the shared context from a background thread.

At some point since updating to Mojave, rendering to the window started to fail utterly; I was left with a completely empty window. Previously, I did this:

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);                                                                                                                                                            
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);                                                                                                                                                            
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);                                                                                                                                                       
                           
    g_sdlWindow = SDL_CreateWindow("", x, y, width, height, videoFlags);                                                                                                                                             
    m_sdlGlContext = SDL_GL_CreateContext(g_sdlWindow);                                                                                                                                                              
    if ( !m_sdlGlContext )                                                                                                                                                                                           
    {                                                                                                                                                                                                                
        vsLog("Failed to create OpenGL context??");                                                                                                                                                                  
        exit(1);                                                                                                                                                                                                     
    }                                                                                                                                                                                                                
                                                                                                                                                                                                                      
    m_loadingGlContext = SDL_GL_CreateContext(g_sdlWindow);                                                                                                                                                          
    if ( !m_loadingGlContext )                                                                                                                                                                                       
    {                                                                                                                                                                                                                
        vsLog("Failed to create OpenGL context for loading??");                                                                                                                                                      
        exit(1);                                                                                                                                                                                                     
    }
    SDL_GL_MakeCurrent( g_sdlWindow, m_sdlGlContext ); // set back to the main context                                                                                                                                        

With this code, none of my drawing would reach the screen ever.

If I switched things around so that I created the loading context first, and then the regular sdl context later, so that I didn’t have to call SDL_GL_MakeCurrent() that one time, then everything worked fine.

I’m… kind of hoping that this weirdness with the call to SDL_GL_MakeCurrent() resulting in no output to the window ever might be a thread to pull on which leads to the weird “rendered OpenGL framebuffer doesn’t make it to the screen most of the time” problem? Interested in whether all this means anything to anybody else, or suggests an avenue I might investigate. Again, I’m on absolute latest SDL code from the repository; am entirely happy to go hacking on the code itself.

Hi,

I think you may be running into the same issue I was running into previously. See the bug report over here,

https://bugzilla.libsdl.org/show_bug.cgi?id=4435

A fix exists, which is a work around for a bug introduced by Apple. If you’re building SDL from source, you could try adding,

-(void)setLayer:(CALayer*)layer
{
if (layer == nil)
return;

[super setLayer:layer];

}

to the code of SDLView and see if that works. It would fix your problem locally only of course, until this fix makes it into SDL.

I distribute a SDL dylib with my program, so… locally would be good enough for me!

Sadly, this patch didn’t resolve the issue for me. I still get (for example) only about four updates actually drawn to the window when my rendering code claims that it’s drawing at about 40 fps.

Interestingly, turning vsync off seems to make the apparent frame rate even lower, while the rendering code claims to be presenting slightly more frames.

Hi,

I tried to reproduce the problem with the following (self contained) code, but was unable to. Does the below code work for you? It should show a flickering red window (at around your refresh rate).

#include <SDL2/SDL.h>
#include <SDL2/SDL_opengl.h>

static void testContext()
{
const int x = 100;
const int y = 100;
const int width = 640;
const int height = 480;
const int videoFlags = SDL_WINDOW_OPENGL;

SDL_Init(SDL_INIT_VIDEO);

  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);

auto * g_sdlWindow = SDL_CreateWindow("", x, y, width, height, videoFlags);
auto * m_sdlGlContext = SDL_GL_CreateContext(g_sdlWindow);
if ( !m_sdlGlContext )
{
    printf("Failed to create OpenGL context??");
    exit(1);
}

auto * m_loadingGlContext = SDL_GL_CreateContext(g_sdlWindow);
if ( !m_loadingGlContext )
{
    printf("Failed to create OpenGL context for loading??");
    exit(1);
}
SDL_GL_MakeCurrent( g_sdlWindow, m_sdlGlContext ); // set back to the main context

for (;;)
{
  glClearColor((rand() % 100) / 100.f, 0.f, 0.f, 0.f);
  glClear(GL_COLOR_BUFFER_BIT);
  
  SDL_Event e;
  if (SDL_PollEvent(&e))
  {
  
  }
  
  SDL_GL_SwapWindow(g_sdlWindow);

}
}

The code you’ve posted gives me a window with no contents whatsoever. If I have Mojave set to ‘dark mode’ then it’s a dark grey (which isn’t black), and if I have it set to ‘light mode’ then it’s a light grey (which isn’t white).

If I swap the creation of the loading context and the regular context and remove the call to SDL_GL_MakeCurrent() (or in fact, simply comment out the call to SDL_GL_MakeCurrent() without making any other changes) , then it works as expected; I get the red flickering that you describe.

This is with the current head of SDL from the repo, plus your setLayer addition from the previous message.

Edit: More details just in case they matter to anybody.

I’m currently testing on a MacBook Pro (Retina, Mid 2012); that’s the one with the NVIDIA GeForce GT 650M 1 GB discrete card in addition to Intel HD Graphics 4000 integrated. 16 GB RAM. Running macOS Mojave 10.14.4. Same behaviour is seen if I reboot, and if I reboot while resetting PRAM/NVRAM.

I’ll be able to test on another machine tomorrow, but for right now, this is the only machine I have access to for testing.

Further details:

I compiled that code up on another Mac; a Mac Mini from 2012, running 10.14.2.

Worked just fine.

I then updated that Mac Mini from 10.14.2 to 10.4.4, ran that same already-compiled program and I got the problematic behaviour. Whatever the problem is, it appears to be a change introduced in either OS X 10.14.3 or 10.14.4. (I’ve been unable to test 10.14.3)

Under 10.14.4, it looks like this:

For convenience, I’ve created a git repo with this test program, including a CMakeLists.txt file to make it easy to build, and adding code to exit when a key is pressed, so you don’t have to kill it from the command line any more.

That repo is over here: https://github.com/vectorstorm/sdl2_osx_multicontexttest

Thank you for this. The information on the OSX version numbers is very helpful. I’m on OSX 10.14.2, which may explain why I’m not seeing this. I use this machine from day to day and need SDL_GL_MakeCurrent to work properly for the code I’m currently working on. So you can imagine I’m not super eager right now to update (since there is no way to roll back I think?).

With Mojave Apple changed a lot of backend details in their OpenGL implementation. A major change is that all views are now layer-backed. I believe the bugzilla report I linked to contains more details. The issue you see does seem related, as the symptoms are very similar. What I did before was link against SDL sources directly so I could debug SDL on the source code level, and inspect internal members of NSOpenGLContext, etc to see what it’s doing. There’s some trickery in Apple’s NSOpenGLContext to dynamically allocate and manage view layers for the NSViews it gets attached to. By inspecting pointer addresses I was able to see at least it wasn’t properly assigning layers when a single context was used for two or more views. Perhaps something else is screwy when two contexts are bound to the same view, or when resources are shared.

By the way, did Xcode also get updated in the process? I’m also still using a slightly outdated version of Xcode, since upgrading it would break another one of my projects.

Well, I don’t use Xcode itself; I just use the command-line tools. And those definitely did get updates as part of the 10.14.2->10.14.4 upgrade process.

So… yeah, think very carefully before committing to an upgrade!

I might take a stab at debugging what’s going wrong over the weekend.

1 Like

One more intriguing piece of information:

contextOne = CreateContext()
contextTwo = CreateContext()

The above pseudocode results in correct drawing on 10.14.4 (as we’d already noted)

contextOne = CreateContext
contextTwo = CreateContext
MakeCurrent( contextOne );

The above pseudocode results in no OpenGL output reaching the screen (but OpenGL framebuffers are being drawn into, as can be shown by reading the framebuffer content back using glReadPixels())

Here’s the new piece of information:

contextOne = CreateContext
contextTwo = CreateContext
MakeCurrent( contextOne );
MakeCurrent( contextTwo );

This results in drawing being visible in the window again. So… it seems like the issue must be something that’s happening when a context is first created and set on the window, which isn’t happening when it’s simply set after having already been created.

Am investigating in that direction now.

And, I have a workaround!

In SDL_cocoaopengl.m, around line 130, there’s this block of code inside SDLOpenGLContext setWindow:

if ([self view] != [windowdata->nswindow contentView]) {
    [self setView:[windowdata->nswindow contentView]];
    if (self == [NSOpenGLContext currentContext]) {
        [self update];
    } else {
        [self scheduleUpdate];
    }
}

If I change this block of code to instead be:

//if ([self view] != [windowdata->nswindow contentView]) {
    [self setView:NULL];
    [self setView:[windowdata->nswindow contentView]];
    if (self == [NSOpenGLContext currentContext]) {
        [self update];
    } else {
        [self scheduleUpdate];
    }
//}

With this code, everything works correctly with multiple contexts again. Note that it’s necessary to set the context’s ‘view’ to NULL before setting it back to the correct value, or it never calls into ‘setLayer’ on the SDLView;

I presume that perhaps in 10.14.4(ish) a test was added in [NSOpenGLContext setView] to see if the new view is the same as the old view, and doing an early exit if so. Setting it to NULL forces the context to call through and set up the view again. (question: should we be setting the context’s view to NULL when a different context is set? It kind of surprised me to see that we left it still pointing at the same window and view, even when it wasn’t current… but this sort of under-the-hood system detail isn’t my strong point at all, especially in Cocoa!)

Note that this doesn’t fix the problem for my actual program, where I really do use both contexts, from different threads; as soon as I set the second context ‘current’ from the background thread, I stop getting rendering results in the window from the main context. More poking required.

Further update:

Updating the code to this:

if ([NSThread isMainThread]) {
    [self setView:NULL];
    [self setView:[windowdata->nswindow contentView]];
    if (self == [NSOpenGLContext currentContext]) {
        [self update];
    } else {
        [self scheduleUpdate];
    }
}

This now works even in my main program’s case, under Mojave 10.14.4; I can set the GL context as often as I like from the main thread, and the window shows whichever context I’ve most recently set as current. If I set the GL context from a background thread, then that background thread successfully gets its context set and can issue OpenGL instructions to it, without its context taking over control of the window.

(This does not however, solve the initial issue from the start of this forum thread, that of only some frames making it visibly to the window, when we’re under load; I still often get only about 10% of rendered frames showing up in the window, and I don’t know why.)

1 Like

And one final update.

ALL of these issues are worked around if you simply build against an earlier version of the MacOS SDK, including both the “no OpenGL visuals appear in window if you ever call SDL_GL_MakeCurrent()” AND the “some percentage of frames don’t appear in the window if GL is doing non-trivial amounts of work” problems, even if the build machine is running the latest MacOS version.

I grabbed a copy of SDK 10.9 and built against that, and all was fine again (apart from the SDL window not obeying dark mode… but… I’ll put up with that for usable OpenGL rendering!)

So I’m probably going to just go with that for my Mac builds, rather than spending any more dev effort trying to figure out what SDL needs to do to play nicely with the latest SDKs. If somebody else wants to try to figure out nice solutions, I’ve left some starting points above!

I’ve avoided updating to Mojave because of the various issues. Building against SDK 10.9 sounds like a better solution; can you provide more details of how you achieved that (I am using a conventional makefile at the command prompt).

First step: Getting the SDK.

The safe way to do it is to download an earlier Xcode build from Apple directly (https://developer.apple.com/download/more/), and then pull the SDK out of the Xcode app bundle. (requires an AppleID, doesn’t (I think?) require a paid developer account)

Alternately, there are other, less-official places where you can download the SDKs directly, without needing to download Xcode along with them. Which can be convenient if you’d rather download a 30meg file instead of a 3 gigabyte file. That’s up to you.

Second step: actually building against a different SDK.

Well, I’m using cmake, personally, so for me it’s as easy as adding this argument to my call to cmake: -DCMAKE_OSX_SDKROOT=/Users/itsme/path/to/MacOS10.9.sdk, and cmake handles the rest automatically.

From a makefile, I’m not sure how you’d do it exactly; it should just be a matter of configuring your include directories and library search paths to look for files inside the different SDK, instead of in /Applications/Xcode.app/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/ (or whichever sdk your Xcode comes with).

If you’re using Xcode itself, then your best bet is the Apple downloads link above, where you can download the appropriate Xcode for any SDK version you want, and it’ll all just work, as long as you’re using the old Xcode version (modern Xcode builds apparently no longer support building to target old SDKs, so you need to instead use old Xcode builds. Or at least, that’s what I read on the internet)

The good news is that you can test all this out before upgrading to a new MacOS; don’t do what I did and upgrade and then try to figure out whether it’s possible to build using an old SDK! Prove to yourself that you can do the build the way you want to first before risking an OS update to your dev machine; it’s really hard to roll back OS upgrades on Mac!

According to this it looks like I need to use the -isysroot compiler flag.