I am porting an application from MS Windows to macOS. One primary feature is an "Image Viewer" that displays a rendering of a CCD image (e.g., astronomy picture). On top of this rendering is overlay text, such as field of view, object details, etc.
In my first pass, I went the route of implementing the rendering and overlay code inside of the "drawRect" function. This meant using CGContextDrawImage, passing it the bitmap data, and then applying the overlay on top of that (all inside drawRect). The result produced very sharp text and graphics, but I could tell that when panning (with mouse) it had just a hint of lag and also some ghosting effect (page tearing?). So I wanted to improve on that.
Enter "layer-based" view approach. First, in my custom NSView, I overrode "wantsUpdateLayer" so that the "updateLayer" function would be called. This allowed me to supply the bitmap data directly to the contents of the NSView layer. Wow - no more lag, no more ghosting artifacts - it was very snappy and excellent fidelity. This is definitely the right direction. But.... now I cannot draw the overlay information! Because "updateLayer" allows only for the setting the bitmap -- it does not allow any drawing code to take place (there is no active CGContext available).
So a bit more exploring and I came up with one possible solution to create two custom CALayer objects. One I call "MyLayerBitmap" and the other "MyLayerOverlay". The former responds to the "display" override which is analogous to the "updateLayer" override from before, and it simply sets the contents to the bitmap data. Simple enough. The latter responds to "drawInContext" which has a supplied CGContextRef, the theory being that here I could do the overlay drawing in this layer. So, I wired up it as follows:
m_layer1 = [MyLayerBitmap layer];
m_layer2 = [MyLayerOverlay layer];
[m_layer1 setParent:self];
[m_layer2 setParent:self];
[m_layer1 setNeedsDisplayOnBoundsChange:YES];
[m_layer2 setNeedsDisplayOnBoundsChange:YES];
m_layer1.frame = self.bounds;
m_layer2.frame = self.bounds;
[m_layer1 addSublayer:m_layer2];
[self setWantsLayer:YES];
MyLayerBitmap:
- (void)display
{
NSLog(@"MyLayerBitmap display is called");
self.contents = (__bridge id)[m_pParent getImgRef_Main];
}
MyLayerOverlay:
- (void)drawInContext:(CGContextRef)ctx
{
NSLog(@"MyLayerOverlay drawInContext is called");
NSGraphicsContext *myCtx;
myCtx = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:true];
[NSGraphicsContext saveGraphicsState];
NSGraphicsContext.currentContext = myCtx;
...drawing code here...
[NSGraphicsContext restoreGraphicsState];
}
Whenever mouse move occurs, I call:
m_layer1.frame = self.frame; // Not sure why, but the first layer has to use frame rather than bounds
m_layer2.frame = self.bounds;
[m_layer1 setNeedsDisplay];
[m_layer2 setNeedsDisplay];
But I now have two problems:
- The overlay text looks blurry. Is CALayer just creating a "one-size-fits-all" bitmap and then stretching it? Or is there any control of how this is done? I use the exact same drawing code as I did in drawRect, the difference is having to use the supplied CGContext.
- The second layer does not receive any display change notification on resizing the view... Only the first layer. This seems like a bug since I specifically set "setNeedsDisplayOnBoundsChange" to YES for both layers. So what happens now is whenever the view is resized, the overlay graphics remain in place which is incorrect. Only when the user pans the view does it get updated.
Here is the layer-based approach, it is fast but blurry:
Here is the "drawRect" approach, it is slower but has sharp text:
Does one just have to use drawRect in order to get sharp/crisp graphics? If so, is there any way to optimize it so that CGContextDrawImage could run faster? I ran profiling and saw that the BitBlt consumes more than 50% of the overall processing time, hence the desire to get away from having to use CGContextDrawImage and just set the layer contents to the bitmap.
Any thoughts would be much appreciated. Thanks.