Media • 1:07:35
Core Animation is the layer-based animation system that is revolutionizing applications made for Mac OS X. Core Animation is also the technology underlying the dynamic user experience seen on iPhone. Learn how to delight your users by using Core Animation for a dynamic, responsive user interface and eye-catching animations. This is an advanced session for those of you going beyond the built-in animations provided by Cocoa and Cocoa Touch.
Speaker: John Harper
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
So the guts of the API then is really this kind of layers and animations. And these are basically the main Objective-C objects you're going to be dealing with. So that's what we're going to start talking about in a minute. But just one final thought is that another way of looking at CA is that it's kind of one of the few OS X technologies that really lets you kind of easily combine all the different kind of graphics frameworks that we have available, whether that be Quartz 2D or Quartz Composer or Core Image, OpenGL, QuickTime. You name it, you probably have a way to let you display it through a CA layer.
Okay, so what is a layer? So a layer is the thing that lets you describe your graphical scene. As we want to be able to have lots of these things, we try to make them fairly lightweight. It has a bunch of different properties, so you can describe things like, you know, where is it on screen, what does it contain, how long does it play for, how fast. You can arrange them in a hierarchy, so you can have, you know, you typically have a root layer, and then the root layer has sub-layers, and then the sub-layers have sub-layers, and so on and so forth.
And this content, it's basically just the bitmap in the final analysis, but there's a lot of places that bitmap can come from. So you can have Core Graphics or Quartz 2D rendering, you can have Core Graphics images, you can have OpenGL rendering, you can have Quartz Composer compositions, QuickTime movies. The list is pretty wide-ranging.
And so the kind of the application programming model that we provide you is that you basically get to arrange these little layer objects, you know, to describe exactly what you want to see on screen, and then you kind of hand it off to us, and then we will take over from that point and basically make sure that it's rendered, you know, as often or as seldom as necessary to give you the best output. You know, we care about the refresh rates of the display, how the animations are running, all this kind of stuff. And the idea is we also do that in a background thread so that you really don't have to care, and it never even blocks your application.
Okay, so the second half of the API I'm putting is the animations. And so as I was kind of describing, layers are basically these little bags of properties. So obviously, kind of the obvious way to do animations is that you have things that can describe how properties change over time. So that could be something like, you know, the layer has a position property which describes where it is on the screen. And so you may want to say, you know, starting at time T for the next second, I want this thing to move from here to here.
So that's the kind of thing we let you kind of wrap up in these animation objects. The details here are that, you know, there's lots, well, not lots, but there's a few different types of animation class. And they all do this kind of same thing, but they let you compute those kind of state changes in slightly different ways. So for example, we have this in code of basic animation, which literally does just let you have a formula to position, which is what you want in kind of a lot of, you know, 80% of cases maybe.
Then there's kind of a parallel class called the keyframe animation, which lets you describe these kind of state changes as a kind of a list of keyframe values. And then we will kind of interpolate between the keyframes based on how, you know, based on how far you are along in the timeline. And then finally, there's another type of animation called a transition. And then transition is not used that often, but it's used when you're unable to animate the properties directly. For example, you may have a list.
The filter's on the last, say, and it contains maybe a Gaussian blur filter. And then you change that, and you say, now I want to have a crystallized filter. And obviously, there's no way we can just kind of animate property changes to kind of map from one to the other because they're fundamentally different.
But what we can do is we can add a transition, and that basically takes the before and after kind of image representations of what you see on screen and does some kind of transition effect between them. So these are kind of the things you would see in Keynote, like cube transitions and crossfades and dissolves. And that kind of thing.
Also, animations are not limited to just changing a single property or having a single effect. Just like layers, you can group them hierarchically, and that is typically done for timing control. You know, you can create a group and have a group of subanimations, and then you can kind of define the timing curves on the group rather than each individual animation.
And finally, again, just like rendering, all of these animations run entirely in the background. So once you add an animation to a layer, you can kind of create a subanimation. And then once you add an animation to a layer, then you really don't need to care much about it. You can get notifications or delegate callbacks when the animation starts and stops, and you can remove them and you can query them.
But other than that, you don't have to do anything every frame. Just like you heard in the session earlier this morning, you know, the whole idea is we want to free your application from having to care anything about what happens at frame rate. Because we think we can do it faster.
Okay, so just briefly, this is the kind of overview of all these animation classes. And you don't really need to absorb anything here, just to see that there are, you know, this kind of class hierarchy with different types of animation and different properties. And that's about it. So let's wrap up this section just by giving you a little example of, you know, how do you code to this stuff. So our example here is two halves. The first half is going to create a layer and basically attach it to a parent who we hope is on screen.
So we're going to create a layer. We're going to set its frame, which is its geometry. And in this case, we're going to say, you know, you have this rectangle, which, because we're setting the frame, that is in the parent's coordinate space. And then we're going to give it an image. This is just some CG image we loaded off disk or whatever. And so the final statement there is we're going to add this layer to its parent, to the parent's list of sub-layers. It will be appended at the end of this model one.
And so at this point, assuming the parent is visible, then our layer is now on screen. So that really is what there is to it. Just to make it a little more of an example, then we're going to say, well, now we've added it, we really want it to fade in over the first second. So the way we can do that is by creating one of these animation objects, setting its keypath property to be opacity, because we want to animate the opacity property of the layer, and then giving it a formula to value of zero to one.
So that's going to take... So the way we can do that is by creating one of these animation objects, setting its keypath property to be opacity, because we want to animate the opacity from zero to one over the time period of the animation, which you can see the next line is set to one, i.e. one second.
And since the default start time for animations is when you add it, then when we add that to the layer in the next statement, what will happen is that as the layer appears, because we just added it, its opacity will ramp up from zero to one over a second. Once the animation is finished, it will be deleted from the layer and everything will just go on as normal.
Okay, so now we're going to talk more about the details of how CA works. And the first thing I want to talk about is this idea of having two trees. And so, you know, we think about the layer tree a lot because it's, you know, it's the object to see data structure. It's the thing you can modify.
And so in this case here, we have, you know, several layers and I arranged the hierarchy. And the content, you know, can be different things. It really doesn't matter. But always behind the scenes, we have this thing called the render tree. And the render tree is kind of our lower-level representation of this same data structure. You know, every time you make a change in the layer tree at some point, we're going to have to try and do a corresponding change to kind of update the render tree to be the same thing, effectively.
And this is the thing we actually render from. And the reason for that is that, you know, as I keep saying, we want to be rendering on a background thread at all times. And so we don't want you to be able to lock us out of rendering ever because, you know, that would be bad and we'd drop frames. So the render thread is really this thing that keeps spinning, keeps looking at this render tree and kind of pulling frames of it and putting them on the screen.
So then the question then becomes, I guess, is that, you know, we have a layer tree and a render tree. And how do we get state changes from one to the other? Well, We had this idea of transactions, and transactions obviously come from database theory. And ours is kind of a very weak attempt at writing on their coattails or something, because our transactions are really just a way to batch up state changes in such a way that they get copied from one to the other in a single kind of atomic unit. So what this means for you is that every time you're spinning around your event loop, your run loop in Cocoa or whatever, then you're making changes to these layers for every event.
And so by default, we will set things up so that every time around that event loop, we will create a single transaction, kind of batch up all the changes, and then when your event loop gets back to the run loop, we can say, oh, we saw they've done kind of some unit of stuff, and we will take that stuff and throw it over the wall to the render tree. So that's kind of what we call an implicit transaction, but that's not really necessary to know that right now. But the thing to take away is that this doesn't have an automatically... Sorry, I just said it did have an automatically.
So the thing to take away is that there are two trees, and every now and then you may have to care, although hopefully not very often, that you have to push things from one to the other. And like I said, it does have an automatically most of the time. So next I want to talk about geometry.
Obviously, geometry is very important for layers, because geometry is how things are arranged on screen, and without this, we would see nothing. So every layer has a bounds rectangle, and the bounds rectangle, which is the bounds property of the layer, defines kind of the layer's internal coordinate space. What that means is that any drawing you do within the layer, or any positioning of sublayers, is within that kind of local coordinate space defined by the bounds rectangle.
And you can see in the picture here, we have a dotted line, or a dotted box, which is trying to represent our superlayer, and we have a smaller graphic, which represents the layer we're talking about. And you can see we've highlighted its width and height, and those are the two subparts of the bounds rectangle.
So every layer also has a position, and the position is not defined in the layer's own coordinate space. It's defined in the parent's coordinate space, the super layer's coordinate space. And what that means is that when you, because keep in mind that all the time we're trying to take this little layer and map it into its super layer. I mean, that's basically what we're talking about here. So the layer has a position, and it also has an anchor point, which is the frame of reference for the position.
And in this case, we've drawn a crosshair in the middle of this little graphic, which is trying to represent that the anchor point is in the center of the layer. And that basically means that all transform and positioning is happening relative to that anchor point center in this case. So you can see that when we take this thing and map it into its super layer, the first thing we do is align the anchor point in the layer eye, the center, with the position in its super layer.
Next, there's also a transform property on the layer. What the transform does is once it's been positioned, it gives you a chance to kind of scale and rotate and all those kind of fun 3D things. So you can see that the transform gets applied once it's been positioned, and in this case, we're just going to rotate and scale a little bit. And that's basically the 90% cases of all the geometry stuff you want to care about. Obviously, we've only talked about 2D right now, but 3D works in just the same way. It's just that the matrices have an extra column or something.
So now we talked about how this layer graphic gets mapped from one from itself its own coordinate space into its parent. So let's talk a little bit more about you know what is in that layer. So there are a number of sub components which come together to form the layer image as I like to call it and so these are things like the background color property of the layer, the contents, the sub layers and the border color. In this graphic they're actually flipped upside down for some reason but basically they're sover together, sover compositing to give you one image representing all of the things in that layer.
So you know if you have any drawn content and a border then obviously the border will be layered on top of the drawn content. So at the end of that we have an image but what do we do with that image? Well there are a number of other properties which can then modify this thing we're creating. So for example we can apply a list of core image filters to do things like you know blurring the contents of the layer or changing its color for some reason.
We can also apply a 2D drop shadow to give you know the standard kind of drop shadow effects and then finally we can apply some kind of opacity fading and this basically are all the effects that run in the coordinate space of the layer itself. So they're kind of the little the things that fit in within that little box of the previous diagram.
So the next part of this trip to the compositing model, I guess, is the compositing model itself. And typically what you see on screen is the 90% case again. We have the layer image, which is that little thing we were just talking about. We have the superlayer transform, which is some matrix we're constructing to represent all of that geometry mapping we were talking about. And then we have the backdrop. The backdrop is everything that has been rendered up to this point from its sibling layers.
So if you have a parent layer with five sublayers, then we're going to render the first one, then the next, and the next. And then for each one, the things that have been rendered before it constitute its backdrop. And so you can see that typically we just do source over compositing, because again, that's what you want most of the time. But there are a number of ways in which we can modify this diagram. So firstly, you can add a compositing filter, which is just a regular core image filter with two input images.
And the idea of this is that you can replace that source over compositing by something more complex. So you could think, I think there are core image filters which represent all of the, for example, the PDF blend modes and the Portaduff blend modes. And you could even invent your own here. So that's the idea.
So the next thing is we can add a list of background filters. And the background filters run on the backdrop of the layer. So it's kind of under siblings. And this, again, is a list of filters. And they're applied 1 through n. And then the result of that is kind of fed into the compositing filter.
So far, we've only talked about two of these image inputs, but there's actually a third one. You also have a mask. Every layer has a mask, and it comes from two separate components. The layer has a mask layer, potentially, and it also has this property called masks-to-bounds. And if masks-to-bounds is set, then it says there's an implicit mask, which is the shape of the layer.
And if the mask layer is set, then it says, you know, take the alpha channel of this layer and treat that as the mask of the layer itself. And so both of those are set. We'll just multiply them together to get, you know, the cumulative mask. And so what do we do with this? First of all,
[Transcript missing]
Timing is a very important part of Core Animation because, you know, we're animating, so we have to have some kind of time base. So to that end, every object in our object hierarchy, be that layers and animations, they all have local, what we call timing spaces, just like they have local coordinate spaces.
And the way we deal with that is when we start to render a frame, you know, we have a global time, which is wall clock time typically. And so just like we map geometry across the layer tree, we also map timing or time. So we'll start at the root and then for every layer, we'll map the time from its parent into itself and then into its sub layers. And so this lets you do very interesting things because the timing model is pretty rich.
and his list of properties on the bottom bullet, I guess. So, you know, things you can set are things like, you can set the delay, which is the begin time. You can set the duration, which is the length of time the thing is gonna run for. And then you can do some other things like, you know, change the rate at which it progresses, change the number of times it repeats, and some other things. This scary looking block of code is basically the entire timing model.
So it's really just here to show you that, although this stuff may seem complex, it's really kind of easy to reason about because typically you only need to look at some little parts of it. So for example, if you wanted to pause a movie or something, you know, some open jail rendering or a course composer, then by looking at this little kind of math, you can see that what you do is set speed to zero on the layer and then change the time offset to kind of push you along the timeline. Obviously, when you set speed to zero, because it's multiplying the parent time, it's gonna clamp you to the start time.
The rest of that code is basically just dealing with repeats and playing backwards and forwards. So now we can look at an example of all this. So here we have some kind of object, you know, the round dot. And we have its timeline. And the timeline is going left to right.
And you can see in this block there's kind of a diagonal line showing progression of local time. So that's all one that we have. An object that's going to run for 20 seconds. And so let's add a child. The child's timing properties are a little different in that it's set to begin two seconds after its parent began.
is the creator of Core Animation. He is the creator of the first ever video game, and - So adding a third sub-layer,
[Transcript missing]
Okay, so again, thinking about time, I wanted to talk about how animations are applied to layers. Now, we have this idea of a sandwich model, which basically comes from Smile. Smile is a web, W3C animation standard, and they kind of first, at least what I first saw it. And the idea is that you have model layers and presentation layers.
Now, the model layer is the thing you modify in your data structures. That's why it's called the model, it's your data model. At least for the graphics. And the presentation layer is kind of a version of that with all the current animations applied, which may modify its properties so that things that are animating from A to B have intermediate values to that position, for example.
And then in between those two kind of bottom to top of the sandwich, we have all the animations that have been attached to this object. So in this case, you can see we have four and they have various timelines. Again, time is left to right. And so then it's pretty obvious, I guess, how we're gonna evaluate this, but we're gonna take a slice for a particular time and then sample each of these animation objects for that time. And when we sample them, that basically means doing the interpolation and producing that, and then the value of that interpolated value, which they wanna apply into the layer.
And obviously, so we take the model layer, and then after applying all the animation values, we end up with this presentation layer. And that's the thing we're gonna, you know,
[Transcript missing]
This section is trying to get away from this kind of high-level view we've just been talking about and actually go straight to the bottom and talk about lots of little details that will be important to you.
We want to talk about subclassing. So why would you subclass the layer? Typically, you want to subclass because you want to add your own behavior. So two common examples are you may want to add your own drawing code or you may want to add your own layout. And by layout, I mean potentially adding sublayers and positioning them somewhere within your local coordinate space.
So obviously the way we do this is subclass in the normal way, in the normal Objective-C way, and then add properties. Because if you want to have behavior, you typically want to customize that somehow. So in this case, we're going to add a single floating point property in that interface.
And then in the implementation, we're going to do something slightly different in that we're not going to try and implement it ourselves. We're going to allow Core Animation to do that for us. And so by declaring the property dynamic, it tells CA and Objective-C that you don't have to do this at compile time, but at run time, we will synthesize all the methods or everything that has to be done to make this property actually exist.
So the next thing you may want to do is look at this default value for key method. Default value for key is a way of providing the initial value of your property. If you don't do this, you'll just get some kind of zero-like value or identity-like in terms of matrices. But if you want to have it default to something other than a zero, this is the way to do it because it's much better than actually setting the value to be its default value because there is no storage associated with this.
So anyway, so you would override the method, check the key that you're giving isn't my property, and if it is, return some value. And then any objects you instantiate from this class, if you actually looked at the property values after being created, they would have whatever default you set.
The next thing you can look at in subclassing is the Will and Did Change KBO methods. These are a really good way to get notifications when your properties have been changed. Continuing with this line width example, if our line width changes and presumably we want to use this to draw a bunch of lines using Core Graphics, then we really need to tell the layer, "You need to redraw yourself because you're out of date now." Again, CoolSuper.
And finally, you know, get to the meat of this in that the reason we've done everything so far is so we can override the draw method and actually provide our custom drawing code. And so to do that, you will override this drawing context method typically. And so again, with this line width thing, we're going to take the current value of the property, set it as the CG context line width, and then presumably go off and draw some lines.
One thing to bear in mind is that if you don't use the dynamic properties and you do implement them statically using instance variables or something like that, you're going to have to do a lot of work. And you should look at the documentation on the net width layer method because you have to implement that if you want to be able to use kind of presentation layers and animations in the best possible way.
So next, let's talk a little bit more about animation. So mostly so far, we've been talking about animation in terms of objects. Now, objects you create and attach to your layers, and then they will go off and do something. And that's great, and that's an underlying fundamental model, but a lot of the time, you just want to set a property and make it animate. So by default, we have a way of letting you do that, and that's called implicit animation.
And what that really means is that when you set properties of layers, by default, the engine will go away and create one of these animation objects for you, and then attach it to the layer. And so typically, we use basic animations here, which are these from-to animating things, because that's kind of what we want to do. And the from value is always the current screen value, and the to value is obviously the new layer value.
And it's worth emphasizing the screen value thing. What this really means is that we're going to take the value of the property, and then we're going to animate the property at the previous frame, previous transaction, and animate from that. If you've already modified it, so say you were to say letter position equals 100 to 100, and then the next line, letter position equals 200 to 200, you won't get an animation from 100 to 100 to 200 to 200.
You'll get an animation from wherever it used to be to 200 to 200. Because typically, we feel that's really what you want. I mean, you don't want things to jump and then animate from where they are now. You want them to animate from where they were to where they're going to be.
So finally, one more point here is that we're talking about numeric animations so far, but a lot of the properties, as I mentioned earlier, don't support numeric animation, things like filter lists and what have you. And in these cases, the default will be to create a transition here, which will just cross-rate from one state of the layer to the next.
And that's all great, but every now and then, the implicit animations become kind of annoying. You know, every now and then, you don't want every property to animate, or you want it to animate in a different way. And so when that happens, the best thing is to turn them off and then add your own animations or don't add anything at all. And there's a number of ways in which you can add things. Sorry, there's a number of ways in which you can replace the existing ones.
The simplest is to basically just set this transaction property to, say, disable actions, actions are kind of animations. But the other way, if you want a more persistent thing, is you can actually subclass the layer and implement some of these action for key methods, which actually give you a chance to return any animation object for any property change.
Okay, so following on from the animation theme, as I mentioned earlier, one of the things you often want to do is look at the current state of the layer with its animations already applied to it. You know, if you want to hit test, then you need to know exactly where that thing is now, not where it was before the animation started. So what the presentation layer does, the presentation tree, is it gives you a way to get access to these objects.
So typically what you will do is you will get a layer and say, I want to know its current state. So you will then call its presentation layer method. And what that does is it gives you a copy of the layer back. And the copy has already had all the animations applied to it for the current frame, the current thing that's probably on the screen. So then you can go in and look at it and say, you know, if I know I have an opacity animation, then I want to look at the opacity property to see, you know, what is the current value on the screen.
One other interesting point about these presentation layers is they actually form a tree, which is why we call it the presentation tree. And what that means is if you get the presentation layer and then ask it for its sublayers or its superlayer, then those things you get back will also have their animations applied to them.
And that makes it pretty easy to do certain types of operations on the regular layer tree and certain types on the animating layer tree, the screen layer tree. So the two examples we have here, which are the most common uses for this thing, are that you can use it to produce the form values for animations. And this is obviously what the implicit animation does.
So in this case, we say we create an animation, we set its keypad, and then we'll set its form value to be the current presentation value of the property we're animating. And then obviously add it to the layer. And that's exactly what the implicit animation is doing. That's why I was stressing that they animate from the current screen value to the new value, because they use the presentation tree.
So secondly, hit testing is the other big use case for this. So when hit testing, you would just use the normal hit test method, but actually hit test on the result of the presentation layer. And because, as I said, this gets you the animating sub-layers, then you will actually basically hit test across the entire tree in its kind of current screen state. And that will give you back something, and that will still be a presentation layer that you get back. So that's why at the bottom we have this call to the model layer method.
And what that does is it takes the presentation layer we have and gives you back the original underlying model object. So obviously if you're hit testing, you want to find exactly where you are or what you hit. But you typically want to know, you know, what is that in the layers I created, not these ones we've just been copied from somewhere. and again the presentation tree is really why you want to implement that initWithLayer method I mentioned. If you don't do that and you have local state in your layers, then things may not work quite right.
Okay, so now we're gonna switch gears and move away from animations and talk about Tiled Layers. So, you know, we have this base class here layer, and it can display, you know, most types of, well, pretty much any CG image. There are problems in terms of graphics cards. Graphics cards can only really display what they can texture from.
They typically put limits on the size of the images you can display, which is normally somewhat bigger than your screen, but not as big as the largest camera picture today, typically. Other problems we have with the base class layer are that you don't have the ability to provide multiple levels of detail, or you don't have the ability to provide the image data in little chunks. It's all or nothing. So what the Tiled Light does is it gives you a way to define this kind of tiled image pyramid thing.
and that means that we can get the image data in lots of little chunks and we also get them, you know, not all ahead of time but we get those little chunks from you when we know we need them. So you can imagine you could create a million by a million pixel image through one of these tile layers and then, you know, scroll around the screen and we will find out, you know, exactly which tiles we need and then ask you for them at that time. Obviously, that has a lot of advantages.
One big change, I guess, and you have to be aware of is that if you buy into this tile layer model, then you're also buying into the fact that your drawing is now all going to be asynchronous. That means that, you know, your drawing context method will no longer be called on the main thread.
It will be called on some background thread, potentially more than one at a time. And obviously, the reason for that, again, is that, you know, we want this thing to be kind of smooth and fluid and we don't want the UI to block. And we want the images to come in over time and in lots of levels. pieces.
Here's a code example. Basically, what we're going to do here is just create one of these tile layers, set its levels of detail to say we have eight layers in this image pyramid. Obviously, any kind of pyramidal scheme, it's going to be top level will be 100%, then the next one will be 50% scale factors and so on. In this case, we're also going to say bias those scale factors up by three.
What that means is that we're actually requesting the pyramid to store three levels that are greater than 100%. We're also going to get a 200% level, a 400%, and an 800% level of detail. The reason we're doing that is because in this case, we could be drawing things from a PDF or from vector artwork. In those cases, we know that we can draw those as finely as we want at any resolution. If you zoom right in to 1,000%, then you don't want the pixels to be blown up. You want the tiles to be redrawn for the current level of detail.
[Transcript missing]
The main thing about a Core Image, a CI image, is that it's not a Raster data structure. It's not pixels. It's kind of a recipe to produce pixels. And that's great and has a lot of advantages. But it's not so great for Core Animation because Core Animation is totally built around this idea of having things we can give the graphics card to texture from. So there is no way to just take a CI image and put it into a layer. You have to draw it into that layer somehow.
And the most simplest way we can find to do that is basically just create a tile layer and just draw the CI image into that tile layer. The other method you could use is using OpenGL. And in some cases that can get you a little more performance. But again, it has all these kind of GPU limits and you have to use OpenGL. You have to be aware of if I create an image 10,000 by 10,000, is that going to work on my current GPU? So really we recommend you just use the tile layer.
And then maybe think about other options if that really doesn't cut it for you, which we think it will. So back, this is the kind of following from the previous example. So here we're going to actually implement the draw method to draw a CI image into our tile layer. And you can see it really has literally one long line of code. So we're going to take the CI image and draw it into the tile layer.
And because before the tile layer handed us off our CG context, it actually set up the scale factors and the clipping rectangles and all the kind of CTM stuff. So all you really have to do in the tile layer is just draw whatever you're going to draw. And then everything just works and you only really draw the tile we care about and at the right level of detail. So at this point, I want to show you a quick demo of what I'm talking about.
[Transcript missing]
You can see now we're showing at 12%. This is like 80 megabytes of data. And at all times, you know, things stay pretty smooth and fluid and, you know, the filters kind of still work. You can see right now the GPU is actually being pretty taxed to render these filters. You can see by the length of time it takes the tiles to show up. So anyway, we think the title layer is pretty cool, and we recommend you use it. So back to the slides. Thanks.
Okay, so next topic is 3D because as everyone knows, Core Animation is a 3D engine. Well, it's not, but a lot of the demos we show you may give you that impression. But so we should really stress that CA is a 2D, 2.5D engine, which means that it's lots of planes, but they can be arranged in 3D space. So what I want to talk about is how you actually do that.
You know, how do you create these demos or these planes? And the way you do that is you set up some kind of camera matrix on your container layer. And the way you do that is you use the sublayer transform property. Now, the sublayer transform was expressly added just so you can have some matrix which affects all of your sublayers. And this is basically ideal for doing kind of camera perspective type things because that's really what you want. I guess I should also say that this is applied relative to the center of the layer it's attached to, just like the other transforms.
So in this case, this little code example is going to show you how to set up the typical kind of camera matrix which we use on all these 3D demos. So the first couple of lines are going to create the perspective component of our matrix. Currently we don't have a function to do that for you, but given that it's only one line of code, I'm not sure it's really worth it. So anyway, so you do this minus one over depth thing in the right matrix component.
And this is basically going to get you this perspective effect. Because what this does is the, you know, M three four basically means take the Z component of my model, divide it by something and then add it to something else. And then you get kind of the perspective foreshortening effect.
The next two lines are applying some kind of camera orientation and positioning effect. You do this after the projection, after the perspective. So in this case we have some pre-cooked rotation matrix which presumably we created through the CA Transform 3D Rotate functions. And then we're going to just offset some XYZ amounts and then add that to the sub-layer transform.
Once we've done that, any sub-layer we put, any layer we put as a sub-layer of this guy with the sub-layer transform, when we move him around and rotate him, the perspective effects and the camera effects will all be applied to him pretty much, you know, they just will.
So then the next part is once you have this 3D space, then you need to position things within it. The way you do that is with the standard position property, but also with the Z position. You know, now we're in 3D space, so the Z component of the layers becomes important because that's where the foreshortening and the perspective comes from.
So then you can also use the anchor point again to control the reference point. And in New and Snow Leopard we've added this anchor point Z thing, which means you can actually now get full kind of 3D control of your reference point for transforms. And that's very nice because that means you can now have, you know, 3D layers which rotate about an arbitrary point in space. You know, not their center, but, you know, some point elsewhere. Also, as was mentioned earlier, we're now trying to add some of the 3D layers to the reference point.
So we're now trying to add support for true intersections between layers. So, you know, if you have two layers which kind of look like this, then we will slice them up in the right way and render the slices in the right order. And again, that's something that's expensive for us to do, so, you know, only do that if you have to, but it can make things look a lot better when they intersect.
Okay, so next on our whirlwind tour of random topics is threading. I don't think we've talked a whole lot about this in the past. Basically, you may want to use threads for drawing these days. You have lots of cores, and lots of them are going to be used for data work, but graphics can be threaded as well. To that end, CA Layer is itself basically entirely thread-safe.
This means you can set any property, you can get any property, you can do basically any restructuring, changing the sub-layers, all these kind of things. We will pretty much guarantee that when you do that, you're never going to corrupt our data structures. And we will pretty much do that until you get the right results.
So the one thing that isn't done for you, though, is that when you fetch a property of a layer, the value you get back is not guaranteed to be valid if another thread is setting the same property at the same time. Now, you can imagine two threads come in. They both set the background color of a particular layer. And then one of them fetches that property while the other one is about to set it.
And now you're kind of in this window of uncertainty. And the object you get back may or may not still be valid because, you know, we don't auto-release things when we give them back to you for performance reasons. So it's kind of up to you to guarantee that, you know, anything you fetch remains good for the lifetime you need it.
We now have a way to let you do that, which is by adding these transaction locks. The idea here is that these are the same locks we use internally. You can take the transaction lock, do any kind of read-modify-write style code, and then unlock the transaction lock. It has to be emphasized that this is a spin lock. If you hold this thing for a long period of time, everything is going to grind to a halt in a pretty nasty way.
But the idea is you can at least have enough synchronization to let you query a property, retain it, or set something else based on its value, before you then unlock and then use your retained value or go along and do something else. One other thing worth mentioning about threading is that you may be able to avoid it entirely just by using the set-by-the-key path. Obviously, if you were going to set a sub-property of an object, like a filter, for example, then filters are not thread-safe.
So you can't just go off and set those properties directly if multiple threads are going to be doing that. However, if you use the set-value-of-key-path with the right current construction of a key-path to reference the right property, then we will make sure, because we have copies of all these filters, that we will make sure that everything works as intended.
And I guess the side effect of that is you'll also be able to get implicit animations on these filter properties, because that's kind of done the same way. One more point about threading is that we try to guarantee that when you call, "Send these display on a layer," then we will call your display or drawing context method on exactly the same thread, because that's pretty important if you want to multi-thread your drawing. That also holds for the layout methods and basically any other callbacks we might be issuing.
And finally, this is back to the two trees thing. As I said earlier, we need a run loop to be able to flush out your updates for you. So if you're on a background thread and you're making layer changes, then you may not have a run loop, or if you do, it may not be running.
Or running fast often enough to push your changes out. So the simple solution there is that every time you do something and you're about to block, or sleep, or whatever, then you can just call this transaction flush method, and that will basically push any cute changes that are in your current thread off to the screen. And that's the thing we're going to be doing automatically in most cases, but you need to do it sometimes.
Okay, so most of, up to this point, what I've been talking about has been very general. You know, just CA as an API, no real platform dependencies, apart from things like Core Image. But so, obviously, CA runs on both platforms we have these days, you know, iPhone and Mac. And iPhone is a lot smaller a platform than the Macintosh, the Mac. So, when we took CA to the iPhone, we really decided that, you know, we could only afford to take the bits of it that we really needed.
Or that we really thought that were things we could do with the most possible performance. So, anything else we kind of left behind, at least for now. There's this list of properties which were removed from the Mac version as we took it to the iPhone. You can't do things like rounded corners yet, you can't do borders, masks, shadows, filters, obviously, layout managers.
We really don't think this is a big problem. Most of these things, if we did implement them, they'd be so slow you wouldn't use them anyway. And the other thing is, you know, they're just kind of convenience things. Like, layout managers can be implemented just by subclassing and using the layout sublets method.
So the next point is, I'm sure you're aware of this, but the coordinate system or the coordinate space in the iPhone is upside down, at least to us Mac people. And what that means is that, you know, if you have code that you want to run in both places, then you need to deal with that somehow. And so up to this point, it was kind of hard to do that because if you just flip the transform, that doesn't do the right thing because, you know, your images flip as well.
Whereas images have a native kind of memory ordering, which, you know, needs to remain static. So we added in 10.6 this new property called Geometry Flipped. And what that does is when you set that on a layer, the entire subtree from that point becomes flipped, but only for geometry.
And so the idea here is that you can take code that runs on the iPhone for the producer's, say, layer subtree, and take it back to the Mac, you know, attach it to some layer you have, set the Geometry Flipped property to be yes. And then hopefully everything will just work. work correctly.
This is the idea. So again, it's worth emphasizing that the images will not be flipped by this thing. You know, because images are images and they have just the standard order, and you always want them to come up the right way up. So now let's talk a little bit about performance. And these things apply mostly both to the Mac and the iPhone. Obviously, performance is more of an issue on iPhone because, you know, it has a lot less capable hardware, although it does also have a smaller screen, of course.
So firstly, the number one thing you want to do, if you can, is avoid all off-screen rendering. And what off-screen rendering is, is that every now and then, most of the time we're going along happily using OpenGL when we're rendering things, and we're just taking the layers you give us and just drawing them straight to the screen. But every now and then, there are certain types of effects, which means we have to halt the screen rendering, create a new buffer to draw into, a new image, if you like, draw things into this, and then copy that to the screen.
And that works out fine. It's really still a lot faster than, you know, anything else we could do. But there is a bottleneck there. I mean, it does, you can noticeably see the speed dropping, especially on the iPhone, where it's much more of a cliff than a, you know, bump or something.
So the list of things you should avoid if you want to avoid off-screen rendering is having group opacity effects, where you have, you know, a layer and a bunch of sub-layers, and then you change the opacity of the parent layer. Obviously, what we have to do to render that is we can't just multiply the opacity through all the layers. Because we'll get the wrong result. So we have to, you know, do off-screen rendering.
Similarly, any kind of masking, which isn't particularly simple. Any type of core image filters or shadows, although those are already only on the Mac right now. And certain types of transition effects and certain types of 3D transforms. If you have a layer and a sub-layer and they both have a 3D matrix, then because we're a 2D model, they have to be kind of projected back into 2D space. And then we have to do that twice, then we have to render one of them off-screen.
The next performance rule is minimize blending. Blending is where you see translucency in your UI. The way graphics cards work is if you're just drawing opaque data, you can splat it straight to the destination. But if you're drawing things which are translucent or have an alpha channel, when you're drawing them, the graphics card will have to look at the pixel, take the value of the destination, read it back, and then do some math between the two and then copy that back to the destination again. And that kind of costs quite a lot, especially because it also means that we have to actually draw the things that are underneath translucent content, whereas if we know that things are opaque, then we can just most of the time avoid entirely drawing anything underneath it.
So this is really key, and especially on the iPhone where, again, the hardware is more limited, so the amount of throw rate, the amount of blending bandwidth you have is a lot less. So what do we want to do to avoid this? Basically, we just want to get rid of the alpha channel from our layers. So if we're drawing into the layer, the way we would do that is we will basically set the opaque property of the layer to be yes.
And that basically tells CA that when it's asking you to draw into the backing store of the image of the layer, then don't bother creating an alpha channel. Similarly, if you're providing CG image data by just sending the contents of the layer to this image object, then you need to make sure that it has no alpha channel.
I should just say that setting the opaque property on these kind of layers has absolutely no effect. So how do you create an image without an alpha channel? Well, obviously, if you're reading an image from a disk or a flash or some kind of file, then you need to make sure that the image format does the right thing. So you may want to use a JPEG because they don't have alpha channels. Apparently, there may be some ways you can create pings without alpha channels.
And then, alternatively, if you're going to create the image from data, you know, you have some raster in memory, then just use the image alpha none bitmap formats in CG. That tells us that you have no alpha data. So one thing I should also say is that, you know, if you're drawing into the layer and you, well, if you're, for some reason you can't remove the alpha channel because, you know, you need these things to blend together and performance is still too bad, then you may want to consider collapsing some of your layer trees.
So, you know, anything we can composite at runtime, if it's not animating, you can composite at draw time. So now you can have one layer and draw two images into it rather than having two layers with an image each. And obviously anything you can do ahead of time once rather than us doing it a million times every frame is going to make things a lot faster.
Okay, so, you know, this is kind of hard to actually even discover when this is happening. You just see things are slow. So we do give you some debugging options. And so basically you just set the CA color opaque environment variable on the Mac. And on the iPhone, the way to set the same option is via the Instruments panel. So you can see I've highlighted it on screen here just so you know where it is. And Instruments actually has a number of interesting Core Animation options to do things like, you know, flashing random parts of the layers when you're drawing them.
Highlighting things in other colors based on what's happening. But this diagram really just shows you this opaque opacity blending thing. And so what you can see here is I cooked up some boring Nib file in IB and then put it up on the iPhone and turned on this option. And you can see here that, you know, the green things are good, they're opaque.
And the red things are bad, they're translucent. And so this is what you'll see if you turn on this option with the UI running. And the idea here is, or the goal, I guess, is to make basically get rid of all the red as much as possible. And if you do that, you should see frame rates improve pretty dramatically.
Okay, so we're on to the last section of the talk now. And finally, I wanted to talk about what is coming up in the next OS release. So just a few of the key features. So first of all, we're going to add support for trilinear filtering, and this is really just exposing a graphics card feature. Trilinear filtering is a way to get good quality downsampling of images because it's also called MIP mapping, and the image you pass the GPU then has multiple levels of detail, again, kind of similar to the tile layer.
And the graphics card will select the most optimal level, and this basically means you get better quality images on the screen because there's a lot less alienating if you pick a small image if you need a small image. And also it's faster because you can minimize the bandwidth the graphics card is actually using. So this is very easy for you to set up. It works on all types of layers, or all types of image-based layers, I should say. But the one drawback is that not all graphics cards support this on all images yet.
So you can... see the OpenGL documentation. But basically, if you have a power of two size image, it's 256 by 256, or 256 by 128, or something like that, then it's pretty much guaranteed to always work. If your image is not power of two size, then it will work on some graphics cards, and anything else will just fall back to the regular method, so it's not terrible.
There's also an LOD bias property, and that is kind of a graphics-y term, but it basically means you have the slider, and one end is sharp, one end is blurry, and you can kind of move yourself along the slider. Oh, I should also point out that this now works with Tidal layer. So you can kind of get some level of trilinear three-way filtering by setting that property on the Tidal layer. And in this case, it's not a true per-pixel graphics card-based property.
[Transcript missing]
So, secondly, we added a gradient layer. The gradient layer is just a subclass of the CA layer. And as its name suggests, it will draw a gradient into its shape. So it's just an axial gradient right now. And you get to specify an array of colors, an array of locations for those color stops.
You can say this one is here to here and then here. And you also get to specify two two-dimensional points within the layer shape to give kind of the endpoints of the axis. So then that defines your gradient. And then what we do then is just draw that using the gradient layer.
So, basically, we're just going to use the gradient layer to draw the gradient in the GPU. And obviously, because these are just kind of numeric properties, you can animate all of them using the regular CA animation. So you can animate colors fading in and out, the gradient spinning around or moving the gradient stops. Kind of everything is up for grabs. Another example should be fairly self-explanatory. We're just really creating layers, setting up much properties again. So let's not dwell on that.
So the next new subclass is called the CA Transform layer. This is a little different. So up to this point, we've talked about 3D and we've talked about 2 and 1/2D. And layers, we've always said, are 2D, which means that you take this 2D graphic element and then you push it into its parent in some three-dimensional space and then flatten it into the parent's plane, thus preserving the 2D-ness throughout the entire pipeline. And that's great. It means you can do kind of 2D imaging effects on 3D, pseudo-3D content.
But it means you can't set up very complex hierarchical models of 3D elements and transform them as groups. So for example, you wouldn't be able to create a cube out of six faces and then rotate the entire thing. You'd have to create those six layers and then rotate each one of them separately about some center point. And as you can imagine, that's either impossible or gets really, really complex really quickly. So what a transform layer does is it basically extends the layer model a little bit and says these are layers which are only grouping constructs.
They don't have any content of their own. You set the image to be something, you'll get a warning and nothing will happen. But their geometry does apply into their sub-layers, but without this flattening step. So now you can see that you can do these kind of cube-like effects in this model because you can create a transform layer and give it six faces and animate the rotation properties of the transform layer. And everything will rotate without being flattened into some kind of mushy mess.
The way we do that internally, which is worth bearing in mind for things like depth sorting, is that any sub-layers of the transform layer are effectively hoisted up into its super layer before we do any of this kind of processing. We take the matrix from the transform layer, concatenate it into the layer itself, and then push it all back up to the next level. So we'll have a demo of this in a little bit. Finally, the last feature is our particle systems. You've probably seen these things in any number of movies, and ours is not quite that cool.
It's basically a way to kind of spew out lots of little images and have some kind of control over where they go. And so, you know, you see a lot of the keynote presentations you see at this conference have these little kind of sparkly effects, and that's kind of things you can do with particle systems. So our particle system is structured as another layer subclass called the emitter layer, and then each emitter layer has a number of cells. And a cell is basically a way to kind of a template for particles being emitted.
Now, the layer has what we call an emission shape, which a number of different options, things like, you know, rectangles, circles, spheres, cubes, etc. And then the cells then define how particles are emitted from those shapes over time and at certain rates. And obviously, since the cells have these kind of template-like properties, every particle will have some set of these properties with some kind of randomization factors. And one really interesting feature is that cells are not limited to just emitting particles. The particles they can emit can also emit particles themselves because cells can have subcells and so on.
So you can kind of create these nice organic-like effects with things emitting things emitting things and what have you. Finally, emitter layers are also kind of similar to transform layers in that they have this 2D, 3D option. If you choose by default that 2D, and that basically means that, again, all of their particles are flattened into their layer plane as they're rendered, but if you say, "I want a 3D particle emitter," then what actually happens is they act kind of like a transformer, and their particles are all rendered into the parent, which means they preserve the 3D geometry into whatever else is in the parent. So now, I want to show you a couple of demos of these new features.
Okay, so first of all, this is the example of the transform layer. And so you can see now we have these two cubes. This is a very quick demo. So we have these two cubes. They're spinning. You can see they're spinning separately, but they're living in the same kind of 3D space. And this is really very simple now because we just have animations on the two layers.
But then we thought, well, let's take it up another level and put them inside another transform layer with another cube and then just kind of rotate that as well. And you can see the things inside are still rotating in their own little space, but now they're being kind of modified by the bitcube. And this is the kind of thing which, you know, I'm not even sure if you could do this with the old model, but it would be a lot of math and a lot of pain. Okay. So now let's look at the particle system.
So this is a pretty simple particle system. It has three objects. It has an emitter layer, a cell, which is emitting the first big blobby spark. And then that also has a subcell, which is emitting the spark, the little shower of fireworks. And so we do something where we play with the timing of the subcell so that it only fires for a very brief period of time, when the other one gets to a certain point. And then there's this big kind of shower, because we set the emission rate, which is the thing that controls how many particles are emitted per second, to some big value.
So if I highlight the layer here, you can see that the emitter layer is not clipping right now. But we can turn that on if we want. So now the emitter is clipping its boundaries. And as I said, it's being flattened into 2D space. So you can see it really is acting like a plane.
So, it's kind of more fun though if I turn off the clipping and turn on the 3D. And that doesn't really do anything now because we're face-on, but you can see when I start to rotate this thing that now we have a kind of a 3D kind of particle thing going on here. And I guess I can also animate this thing. So, I mean, like I said, this is very little amount of code, and we have these kind of nice effects going on with nice, lots of smooth animation.
So one other example of these, which is basically the same demo app, so don't expect anything new, is another kind of particle system. Again, we have this kind of rotation feature.
[Transcript missing]
And that's basically done with a timing model where when I'm starting to scrub, I'm just setting the time, the speed of the emission layer to be zero and changing the time offset to clamp me to my current point as I tried to describe earlier. So it's kind of a nice effect.
Okay, so... So with that, I think we're about done. Tomorrow, there's another Core Animation related session, which is Troy's layer-backed view session, which has a lot of NSV type stuff, but also a lot of just general Core Animation things. So you should definitely go to that. Finally, we're going to have a lab after this, which I guess is downstairs in the graphics lab.