Mojave 10.14 Regression: Missing NSGraphicsContext in animateOneFrame

Originator:eric
Number:rdar://45304590 Date Originated:10/16/2018
Status:Open Resolved:
Product:macOS Product Version:10.14
Classification: Reproducible:Yes
 
Area:
Screen Saver & desktop

Summary:
The ScreenSaverView class is missing NSGraphicsContext when calling the animateOneFrame method. The context does exist for drawRect, but animateOneFrame prior to Mojave used to support drawing methods in that function. Many example projects also now fail to render. Using drawRect requires redrawing an entire portion rather than just drawing on top of an existing buffer (desired behavior).

Drawing (2d) in the animateOneFrame method was working prior to Mojave, and appears to be supported by the current documentation. 

From https://developer.apple.com/documentation/screensaver/screensaverview/1512471-animateoneframe?language=objc:
"It is guaranteed that the focus is locked when this method is called, so subclasses may do drawing in this method."

Steps to Reproduce:
(1) Create a new ScreenSaver project with X Code. New->Project->macOS->Other->Screen Saver

(2) Replace the default methods with:
- (void)drawRect:(NSRect)rect
{
    if ([NSGraphicsContext currentContext] == nil){
        NSLog(@"drawRect: Missing graphics context. Unable to draw.");
        @throw [NSException exceptionWithName:@"NoGraphicsContext" reason:@"currentContext is nil" userInfo:nil];
    } else {
        NSLog(@"drawRect: Graphics context exists! Everything is fine.");
    }
    [super drawRect:rect];
}

- (void)animateOneFrame
{
    if ([NSGraphicsContext currentContext] == nil){
        NSLog(@"animateOneFrame: Missing graphics context. Unable to draw.");
        @throw [NSException exceptionWithName:@"NoGraphicsContext" reason:@"currentContext is nil" userInfo:nil];
    } else {
        NSLog(@"animateOneFrame: Graphics context exists! Everything is fine.");
    }
    return;
}

(3) Install the screensaver you built and debug the preference pane or screensaverengine and observe the Exception thrown in animateOneFrame, but not in drawRect.

Expected Results:
No exception is thrown.

Actual Results:
Exceptions is thrown:
...
2018-10-15 10:18:45.388379-0700 ScreenSaverEngine[772:15734] drawRect: Graphics context exists! Everything is fine.
2018-10-15 10:18:45.411356-0700 ScreenSaverEngine[772:15734] animateOneFrame: Missing graphics context. Unable to draw.
2018-10-15 10:18:45.411534-0700 ScreenSaverEngine[772:15734] [General] currentContext is nil
2018-10-15 10:18:45.414369-0700 ScreenSaverEngine[772:15734] [General] (
    0   CoreFoundation                      0x00007fff2b6f343d __exceptionPreprocess + 256
...

Additional information not in the original radar:

The following work-around appears to be working well, albeit at a cost of adding a buffer, but I'm not expert at Apple's CG framework, so there maybe some issues with doing this. The following functions are excerpts from the implementation of the class derived from ScreenSaverView for my application. The bitmapBuffer and ctx vars were declared private on the interface, and the original animateOneFrame function code was moved to a newly declared updateAnimation method (which does not exist in the parent ScreenSaverView class). Also ARC is enabled now for memory management. Functionally, with these changes the application behavior matches execution on pre-Mojave installations.

- (void)drawRect:(NSRect)rect
{
    [super drawRect:rect];
    if (bitmapBuffer != nil){
        CGContextDrawImage([[NSGraphicsContext currentContext] CGContext], [self bounds], [bitmapBuffer CGImage]);
    }
}

- (void)animateOneFrame
{
    if (bitmapBuffer == nil){
        bitmapBuffer = [self bitmapImageRepForCachingDisplayInRect:[self bounds]];
    }
    ctx = [NSGraphicsContext graphicsContextWithBitmapImageRep:bitmapBuffer];
    [NSGraphicsContext saveGraphicsState];
    [NSGraphicsContext setCurrentContext:ctx];
    [self updateAnimation];
    [ctx flushGraphics];
    [NSGraphicsContext restoreGraphicsState];
    [self setNeedsDisplayInRect:[self bounds]];
}

Comments

Alternative workaround

The workaround shown here proved problematic for me. An alternative workaround is to move the drawing code into drawRect and trigger a draw by setting needsDisplay to true in animateOneFrame.

By edoernenburg at Sept. 1, 2019, 3 p.m. (reply...)

Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!