Core Animation architecture - macos

From the documentation it appears that core animation layer is above OpenGL and Quartz2D. i.e.
executing a core animation command should produce a sequence of Quartz2D and OpenGL commands Am I right?
In interface builder, under the View Effects tab, we can set the core animation layer. What happens internally there?When we tick Context View option, contents on screen (buttons, scrolls etc) are not drawn using main context or currentContext(view), but new Bitmap Context is created for them. What is happening under the hood there?
Can somebody please explain me relationship between CoreAnimation Layer and Quartz2d/OpenGL?

Core Animation layers are essentially high-level abstractions of OpenGL surfaces. They are stored and manipulated by the GPU and so manipulation of the layers is extremely fast. CALayer objects by themselves are very lightweight and have no event handling.
Layer-backed NSView objects (which is what you get if you enable the checkboxes in Interface Builder) are views that draw their content into a Core Animation layer, again stored in the GPU's memory and with the same performance advantages as plain CALayer objects, but with all the functionality of a normal NSView.
What happens is that the view's content is rendered (via Quartz) to its backing layer (essentially an OpenGL texture). The view then only needs to draw again if the content of the layer changes.
Changes in position, scale, rotation etc of the view's layer do not require the view's content be redrawn. This means that most of the time the CPU does not have to get involved in constantly redrawing the view.

Related

What is the correct way to optimise scrolling performance of NSImageView and NSView

I need to improve the scrolling performance of a view for annotating on top of an image.
Currently I have the following:
- annotationView (custom NSView)
- imageView (NSImageView)
- contentView (custom NSView)
- clipView (NSClipView)
- scrollView (NScrollView)
The images are quite large PDFs and PNGs and scrolling is poor unless I make the imageView layer backed, which I am just doing in Interface Builder. Scrolling is then pretty smooth.
However the PNG images override everything on top of the imageView, whereas the PDF images remain correctly in the background.
Why is this and how can I fix that?
To get even better performance I would also like to make the annotationView layer backed as well but if I do that the entire view becomes black - with the exception of the annotations being draw on the annotationView. How can I make this layer backed view transparent but still allow for the shapes to be draw on it. It seems I can make it transparent but then everything becomes transparent, including the drawn shapes.
Is there a better way to achieve this? The annotations are simply shapes and text that need to be placed at specific positions over the image which I am currently just drawing in response to mouse positions.
The short answer is to use layer backed views and use CGContext for drawing and not the simpler NSView drawing APIs.
By default macOS does not use GPU based graphics, unlike iOS.
macOS provides a high level API that uses the CPU rather than the GPU for graphics operations.
So in my case I switched everything to layer backed NSViews - you can set this in Interface Builder or simply add 'wantsLayer = true' in the NSView initialisation code (init()).
Avoid using NSImageView, instead use a layer backed view and set the layer.content = NSIMage, you may have to also set the layers background colour or you might get some areas of the background not being cleared when scrolling.
This works for me - I have big PDF images in the background - building layouts and a layer backed view on top of that for placing annotations.
Scrolling around is now buttery smooth. Images seem to load instantly.
For the most part its pretty easy - just set up right from the start and save yourself a lot of headaches.

The internals of NSScrollView

When you gently scroll an NSScrollView the rectangle that Cocoa marks as dirty, and passes to drawRect, is often trivially small (perhaps as small as one or two pixels in height, for a vertical scroll view). The framework clearly already knows what the majority of the content is (because it's on screen) and where to redraw it (just the offset brought about by the scroll), so all it needs the developer to do is fill in the small rectangle that's about to appear. I was wondering what's happening behind the scenes to allow this to happen?
For example, if I wanted to implement my own super-smooth scroll view as a learning project, what kind of data would I be recording about the document view to enable me to just re-position - rather than redraw - the majority of it. Is Cocoa constantly generating images on background threads that it draws on screen when required, or is there something a bit more subtle going on?
There's lots going on. If you haven't already read it, you should read the Scroll View Programming Guide for Cocoa.
The copying of the existing rendering is accomplished by -[NSView scrollRect:by:]. It's only done if the NSClipView that's part of the NSScrollView architecture is set to copy-on-scroll (the copiesOnScroll property).
Also, there's "responsive scrolling". Since 10.9, if certain conditions are met, AppKit will speculatively render the document view beyond the visible rect so that, when the user scrolls, it can show the scrolled-in area without asking the document view to render.
You can set your views to be layer-backed. In that case, they are typically rendered to textures and composited by the window server. This means they don't necessarily have to re-draw to render in a new position. It's quite likely that responsive scrolling uses layers behind the scenes to hold the pre-rendered content.

"CoreAnimation: surface is too large"

I'm creating a custom (layer-hosting) document view, which is contained within a scroll view. The root layer has two sub layers of the same size--one for the view's content, and one for anything that needs to hover over the main content. I set the frame to 2500x2500 and added a number of cells to the content layer, which was fine. On adding a translucent clone of one of the cell's layers to the overlay layer, the whole view clears briefly, and I get a log message 'core animation: surface 2502x2502 is too large'. This happens between adding the new layer and the next cycle of the event loop, so I guess when core animation renders the new layer.
I knew that a layer's content size is related to opengl texture size, but didn't think its frame mattered. I'm not drawing anything to these layers, not setting any style properties, and remove offscreen sub layers. All I'm really using them for is to handle the geometry of the document view. Is this an appropriate use of CA layers? If not, are there better ways of handling a large core animation-based document view?
Edit:
I've had this problem again, caused by an implicit animation on adding sublayers to the large parent. So in addition to what is suggested below, that's one to check if you run into this.
I would check to make sure that you're not setting any properties on your 2500x2500 layers which could require offscreen rendering. (This causes the layer to try and create a full-size buffer off-screen and render its contents into that buffer, rather than just rendering the contents to the screen directly.)
For example, setting an opacity, masksToBounds, mask, shouldRasterize, etc, could cause offscreen-rendering. You can see if offscreen-rendering is happening with the Core Animation instrument. (There's a checkbox to highlight offscreen-rendered areas.)

CAOpenGLLayer displaying remote Context

I have a sub-classed CAOpenGLLayer class which overrides drawInCGLContext there I draw a rectangle with OpenGL. The CAOpenGLLayer is added to a CALayer and shown.
So when I would like to draw something I would need to do it in drawInCGLContext with this architecture.
What I would like to have is a sort of context used by an other class to draw, animate or render to but will be displayed every time drawInCGLContext occurs.
So basically the only thing my subclass should do is display a remote (OpenGL)context, what's the best way to achieve this? Or should I consider a different approach?
*Not using a CALayer is not an option.
Have you considered using a frame buffer object (FBO)? You can create one which is backed by a texture. Your "remote" drawing class could draw into the FBO, which will cause the drawing to go to the texture that backs it. You can then use that texture elsewhere, like blitting it to the screen in your CAOpenGLLayer subclass. See this link for details of how to use an FBO.

what is layer in core animation

In core animation or in App kit When we say layer-backed view or simply add a layer in the view,then actually what we mean by the layer.
A simple Google search:
The CALayer is the canvas upon which everything in Core Animation is painted. When you define movement, color changes, image effects, and so on, those are applied to CALayer objects. From a code perspective, CALayers are a lightweight representation similar to an NSView. In fact, NSView objects can be manipulated via their CALayer. This is referred to as being layer-backed.
A CALayer is an object which manages and draws upon a GL surface, and can manipulate that surface's location in three dimensions, without needing to redraw its contents.

Resources