Small change of behavior of NSOpenGLView in Mac OSX El Capitan

In summary, the behavior of NSOpenGLView at the creation has changed a little bit.  In OSX Yosemite and earlier, if you programatically create a sub-class of NSOpenGLView, you could call OpenGL functions right away.  An OpenGL context was ready to go as soon as you create an instance of your NSOpenGLView-derived class.

However, in El Capitan, it looks like the OpenGL context is create asynchronously.  OpenGL context of NSOpenGLView apparently is not ready until the framework calls prepareOpenGL method.  If you are setting up some OpenGL parameters right after creating the view, those settings may be ignored.

That was a surprise.  One of my students notified me that the OpenGL framework that I wrote and have been using in my C++ programming course stopped working when he (she?) upgraded the operating system to OSX El Capitan.

I am still using my Mac mini with Yosemite.  Honestly I wanted to use it with Mavericks, but this Mac mini came with Yosemite, and I could not downgrade.

For some reason, operating-system vendors don't understand that absolutely no user wants a new operating system.  New operating system does more bad than good.  The computer becomes slower.  Software that used to be running just fine stops working.  A perfectly-healthy printer that was serving for more than 10 years does no longer work with a new operating system.  The computer istself may be too old to run the new operating system, and the support for the old operating system is terminated.  New operating system only makes you pay more.  That's the whole purpose of those operating-system vendors.  An operating system needs to be the best supporting actor.  What the users care about is the application that serves for the user's purpose.

Another bad happened again this time.  Well, this time, it was partly due to my sloppy programming, and the fix was easy.  And, YSFLIGHT and other my programs are not affected by this problem, because OpenGL settings are anyway refreshed after the point of problem.

I cannot take a risk of upgrading my Mac mini to OSX El Capitan.  So, I upgraded my old 2008 Macbook Pro to El Capitan.  I don't want a new operating system, but at least I admire Apple for supporting 7-year old laptop.  I then compiled a short program with my graphics framework.  I didn't see any obvious erros, but the window was just black.  I was already suspecting that the problem was because of the asynchronous creation of OpenGL context.  So, I copied the OpenGL-initialization code to the event-polling loop in the main function, and the graphics came back.

In my framework, I made YsOpenGLWindow class derived from NSWindow, and YsOpenGLView class derived from NSOpenGLView.  YsOpenGLWindow is programatically created, and YsOpenGLView was created in the same function and attached to the instance of YsOpenGLWindow.  Immediately after creating YsOpenGLView, I was setting up OpenGL transformations and viewport so that a student can use the window for 2D drawing right away.  It was very important because it is much easier to jump on to OpenGL than first talking about transformations, projections, and viewport and then start drawing something.  It is much more fun if the student can open a window and then draw something right away.

The previous version was creating an instance of YsOpenGLView where highlighted green.  In OSX Yosemite and earlier, an OpenGL context was created as soon as YsOpenGLView was created, and all OpenGL functions were ready to go.  Therefore, I was initializing settings where highlighted orange.  However, in El Capitan, OpenGL context is not ready until the application framework calls prepareOpenGL method of NSOpenGLView class.  Therefore, all initialization in here was ignored.

void FsOpenWindowC(int x0,int y0,int wid,int hei,int useDoubleBuffer,int useMultiSampleBuffer,const char windowTitle[])
{
    #if !__has_feature(objc_arc)
    NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
    #endif

    [NSApplication sharedApplication];
    [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];

    ysDelegate=[YsMacDelegate alloc];
    [ysDelegate init];
    [NSApp setDelegate: ysDelegate];

    [NSApp finishLaunching];

    NSRect contRect;
    contRect=NSMakeRect(x0,y0,wid,hei);

    unsigned int winStyle=
       NSTitledWindowMask|
        NSClosableWindowMask|
        NSMiniaturizableWindowMask|
        NSResizableWindowMask;

    ysWnd=[YsOpenGLWindow alloc];
    [ysWnd
        initWithContentRect:contRect
        styleMask:winStyle
        backing:NSBackingStoreBuffered 
        defer:NO];

    int nFormatAttrib=0;
    NSOpenGLPixelFormatAttribute formatAttrib[32]={0};
    AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLPFAWindow);
    AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLPFADepthSize);
    AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,(NSOpenGLPixelFormatAttribute)32);
    if(0!=useDoubleBuffer)
    {
 @      AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLPFADoubleBuffer);
    }
    if(0!=useMultiSampleBuffer)
    {
        AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLPFAMultisample);
        AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLPFASampleBuffers);
        AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,(NSOpenGLPixelFormatAttribute)1);
        AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLPFASamples);
        AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,(NSOpenGLPixelFormatAttribute)4);
    }

    NSOpenGLPixelFormat *format=[NSOpenGLPixelFormat alloc];
    [format initWithAttributes: formatAttrib];

    ysView=[YsOpenGLView alloc];
    contRect=NSMakeRect(0,0,800,600);
    [ysView
        initWithFrame:contRect
        pixelFormat:format];
    
    [ysWnd setContentView:ysView];
    [ysWnd makeFirstResponder:ysView];

    [ysWnd makeKeyAndOrderFront:nil];

    [ysWnd makeMainWindow];

    [NSApp activateIgnoringOtherApps:YES];

    YsAddMenu();

#if !__has_feature(objc_arc)
    [pool release];
#endif

    int i;
    for(i=0; i<FSKEY_NUM_KEYCODE; i++)
    {
        fsKeyIsDown[i]=0;
    }

    glClearColor(1.0F,1.0F,1.0F,0.0F);
    glClearDepth(1.0F);
    glDisable(GL_DEPTH_TEST);

    glViewport(0,0,wid,hei);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0,(float)wid-1,(float)hei-1,0,-1,1);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glShadeModel(GL_FLAT);
    glPointSize(1);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glColor3ub(0,0,0);
}

To fix it, I added a static variable at the beginning of the source file as:

    static bool openGLprepared=false;

And then moved the initialization code to prepareOpenGL method of YsOpenGLView as:

@implementation YsOpenGLView

-(void) prepareOpenGL
{
    printf("%s\n",__FUNCTION__);

    NSRect rect;
    rect=self.frame;
    int wid=rect.size.width;
    int hei=rect.size.height;

    glClearColor(1.0F,1.0F,1.0F,0.0F);
    glClearDepth(1.0F);
    glDisable(GL_DEPTH_TEST);

    glViewport(0,0,wid,hei);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0,(float)wid-1,(float)hei-1,0,-1,1);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glShadeModel(GL_FLAT);
    glPointSize(1);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glColor3ub(0,0,0);

    openGLprepared=true;
}

.... OK.  Looks like openGLprepared is not used outside YsOpenGLView.  I could have made it a member variable of YsOpenGLView.  But, anyway it's ok.  I just don't want to waste time for learning Objective-C which is inferior to C++.

Seriously, becoming proficient in one programming language takes much more than learning syntax.  It takes long time to learn and understand best practices.  If you spend long time for a programming language that is specific to one vendor you are taking a big risk.  I am too scared to spend time for a trendy programming language that has just popped up and may disappear any time.

Well, maybe it's high-risk and high-return.  If you become proficient in something trendy faster than other people, you make big money.  Now I understand.  My salary doesn't go up because I am not taking a high risk.  But, is the life a gamble?  In some aspects, it is.  Everything in this world is statistical.  But, is the life essentially a gamble?

Then, I fixed FsOpenWindowC function.  I also specificly specified a parameter for what version OpenGL I need to use.  FsPollDeviceC checks for the events and invokes call-back functions.  FsOpenWindowC waits until openGLprepared is set to true from prepareOpenGL method.  However, I don't want my program to fall into an infinite loop, so it waits up to 2 seconds.  As long as the function doesn't fall into an infinite loop, there is a chance of recoery.

Since FsOpenWindowC waits until the OpenGL context is ready, the program can call all the OpenGL functions as soon as FsOpenWindowC returned.

void FsOpenWindowC(int x0,int y0,int wid,int hei,int useDoubleBuffer,int useMultiSampleBuffer,const char windowTitle[])
{
#if !__has_feature(objc_arc)
    NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
#endif

    [NSApplication sharedApplication];
    [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];

    ysDelegate=[YsMacDelegate alloc];
    [ysDelegate init];
    [NSApp setDelegate: ysDelegate];

    [NSApp finishLaunching];

    NSRect contRect;
    contRect=NSMakeRect(x0,y0,wid,hei);

    unsigned int winStyle=
        NSTitledWindowMask|
        NSClosableWindowMask|
        NSMiniaturizableWindowMask|
        NSResizableWindowMask;

    ysWnd=[YsOpenGLWindow alloc];
    [ysWnd
        initWithContentRect:contRect
        styleMask:winStyle
        backing:NSBackingStoreBuffered 
        defer:NO];

    int nFormatAttrib=0;
    NSOpenGLPixelFormatAttribute formatAttrib[64]={0};

    AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLPFAOpenGLProfile);
    AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLProfileVersionLegacy);
    // AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLProfileVersion3_2Core);

    // Deprecated? AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLPFAWindow);
    AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLPFADepthSize);
    AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,(NSOpenGLPixelFormatAttribute)32);
    if(0!=useDoubleBuffer)
    {
        AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLPFADoubleBuffer);
    }
    if(0!=useMultiSampleBuffer)
    {
        AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLPFAMultisample);
        AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLPFASampleBuffers);
        AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,(NSOpenGLPixelFormatAttribute)1);
        AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,NSOpenGLPFASamples);
        AddOpenGLPixelFormatAttrib(&nFormatAttrib,formatAttrib,(NSOpenGLPixelFormatAttribute)4);
    }

    NSOpenGLPixelFormat *format=[NSOpenGLPixelFormat alloc];
    [format initWithAttributes: formatAttrib];

    openGLprepared=false;

    ysView=[YsOpenGLView alloc];
    contRect=NSMakeRect(0,0,800,600);
    [ysView
    initWithFrame:contRect
    pixelFormat:format];

    [ysWnd setContentView:ysView];
    [ysWnd makeFirstResponder:ysView];

    [ysWnd makeKeyAndOrderFront:nil];

    [ysWnd makeMainWindow];


    [NSApp activateIgnoringOtherApps:YES];

    YsAddMenu();


#if !__has_feature(objc_arc)
    [pool release];
#endif

    int i;
    for(i=0; i<FSKEY_NUM_KEYCODE; i++)
    {
        fsKeyIsDown[i]=0;
    }

    // 2015/10/04
    // OSX El Capitan creates OpenGL context asynchronously.
    // Therefore, the initialization needs to be delayed until prepareOpenGL.
    time_t t0=time(NULL);
    while(time(NULL)-t0<2)
    {
        FsPollDeviceC();
        if(true==openGLprepared)
        {
            printf("OpenGL context prepared. Good to go.\n");
            break;
        }
    }
}

After adding these changes, my framework worked fine on El Capitan.