I'm in the process of trying to tweak the process of a game which uses Quartz to draw a moderate number of sprites, around 50, during each game loop (40 fps).
At the moment I have a very simple drawing mechanism where a single game view (UIView) iterates over all the active sprites and asks each one to render itself to a CGContext (that of the view). This works fine, but performance is starting to drop with more than 50 active objects and I'd quite like to tweak things.
I've decided that holding a CALayer for each sprite which its cached drawing is the way to go - then using Core Animation to render/rotate/scale the drawing.
I'm struggling to understand how I achieve this. Where exactly do I initially draw to? I don't have a CGContext when my sprite is initialised. Is the right approach to render to a CGImage as a buffer then set it as the content on the CALayer?
Just subclass CALayer and do your drawing in drawInContext:. The layer will then cache its contents automatically. If you don't want to subclass CALayer, you can also assign it a delegate (which must not be a UIView) and implement drawLayer:inContext:.
Related
I make extensive use of -drawRect: to do some nice animations. A timer tries to fire 30 times per second with -setNeedsDisplay, but it feels like just 20 times. Also I can't use -setNeedsDisplayInRect: because the animation covers the entire thing.
Would it help to take some of those drawing operations out of -drawRect: and move them to a subview? -drawRect has to do less then, but instead the OS will have more work with compositing views.
Is there a rule of thumb which one is more worse? I remember from an apple text that they claimed Core Animation doesn't redraw during animation. So is that their secret of speed? Using subviews in animations?
Much of this will be similar to my answer to your other question:
Compositing is faster, by far. On the iPhone, content is drawn into a CALayer (or into a UIView's backing CALayer) using Quartz drawing calls or from a bitmapped image. This layer is then rasterized and effectively cached as an OpenGL texture on the GPU.
This drawing and caching operation is very expensive, but once the layer is on the GPU, it can be moved around, scaled, rotated, etc. in a hardware-accelerated manner. All the GPU has to do while the layer is animating is to composite it with the other onscreen layers every frame. This is why Core Animation can do 50 layers animating around the screen at 60 FPS on even the oldest iPhone models.
Layers and views only redraw themselves when prompted, or if resized when their needsDisplayOnBoundsChange property is set to YES. The drawing system is set up this way because of how expensive it is to redraw and recache the layer contents. If you can, avoid redrawing your layer content regularly, but instead split it into layers or views that you can animate around individually using Core Animation. If you need to animate a changing shape, look to CAShapeLayer, which makes this much more efficient than simply redrawing every frame.
As a worst case, yes, you can take the portions of your view that are static and move them to one view, then have a subview which has only the changing portion of your drawing within it. Performance won't be great, but it will be much better than if you had to redraw everything within the view. Compositing overhead will be negligible compared to the expense of drawing.
Basically, I want to animate a shrinking circle. I've already done this by drawing progressively smaller circles onto CGLayer's, and then using
if(index < 30){
[self performSelector:#selector(showNextLayer) withObject:nil afterDelay:(NSTimeInterval)0.02];
index++;
}
in my showNextLayer method. This works for my simple goal, but in general is this a more or less sensible way to draw a custom animation?
Apple has a lot of documentation on animation -enough to confuse me-, but as far as I -finally- understand:
The animation services provided by UIView is good for animating some basic view properties, view transitions etc.,
those provided by classes that inherit from CAAnimation, are good for animating properties like color change, 3d transformations etc.
UIViewImage is good for animating custom bitmap images (and it is not a good idea to create bitmap images when you can just draw circles into a CGLayer instead).
It seems to me that displaying the contents of a sequence of CGLayer 's would also be supported, am I missing something?
Thanks in advance for any clarification..
I'm not really sure, what am I asking...
...but if make layer composites in separate methods, and draw them to the view also outside of the drawRect: method... ...then could my app performance get raised?
I'm actually subclassed a puzzlePiece:UIView class, where the puzzlepiece gets rendered, but the redraw invoked now with setNeedsDisplay since the drawing implementation takes place in the drawRect: method. The app lags.
Can I improve performance if I get rid of the so called drawRect: method?
The thing wants to get implemented: http://gotoandplay.freeblog.hu/
I think what you want to do here is to have a very single, simple UIView that covers the entire play area. Then put each tile in a CALayer, and attach all the CALayers to the UIView and move them around and rotate them. Then there should not need to be a -drawRect: at all. You'll do all your drawing in your layers, and you should get the best performance that way. CALayer is similar to UIView in principle, but much lighter weight (faster and simpler). CALayer is basically a view that can only draw; it can't handle touches or events. It's parent view has to do that for it. But that let's CALayer be much faster.
As far as I can tell, I need an UIView (or subclass of UIView) to display an CALayer on screen, right?
Then, what's the point of using CALayer for saving memory? The only point I see is when I would add several sublayers to a CALayer. Then those sublayers would not get copied 3 times for all the different tree types like presentation tree, render tree and so on. Is that right?
You can add CALayers as sublayers of another CALayer. Each individual layer does not have to be backed by a view, but the root layer in the hierarchy must be backed by a UIView.
The point is not to avoid having copies of the CALayers, which are quite lightweight, but to avoid having copies of UIViews or, more specifically, the graphics contexts that back UIViews. Those take up considerably more memory.
I have an opengl scene rendering on an EAGLView layer and some other elements (circles and such) rendering on a UIView (which is a sibling of the EAGLView, positioned above it). Is it possible to blend colors between the two layers? I'd like to do some difference blending to get an inversion effect on the colors from EAGLView.
I've been playing around with CGBlendMode but it only seems to affect what I'm drawing in that current view. I think this has something to do with the CGContext but I'm a little hazy on the details, can I force the UIView and the EAGLView to have the same CGContext so that the blending works between them?
Help, corrections, clarifications are all appreciated. Thanks in advance,
-S
Short answer is you can not. Long answer follows.
By EAGLView you must mean the subclass of UIView that is included in the OpenGL ES Template in Xcode. What makes this class special is that the layerClass class method is overridden and return the CAEAGLLayer class instead of the CALayer class, as is default.
UIView and CALayer work in pairs. All UIView objects are backed by a CALayer, the CALayer is the object responsible for layout and rendering to screen. The UIView is a delegate to the CALayer, and is responsible for drawing it's graphics when needed.
CALayer will let it's delegate (the UIView) draw into a CGContextRef. It is one context per UIView, so you can not use CGBlendMode to blend several views since it will only function within one single UIView context.
Blending of CALayer should be done using the filter properties. These are defined for iPhone OS but the available filters are undefined according to the documentation. This is because Core Image is not available on iPhone OS at this time.
I don't think you'll be able to blend colours in that sense. The best you can do is have one completely obscuring the other, or have the top layer semi-transparent (in which case, you'll see the part underneath) - but you won't be able to do XOR type drawing.