I have a UIView with a large number of CALayers (~1000), each of which has a small image as its contents. This UIView is a subview of a scrollview (actually it's a subview of another view which is a subview of the scrollview). This draws relatively quickly at first (couple seconds). However when I scroll in the scrollview the frame rate is very low.
Is it trying to redraw the contents of each CALayer every time I scroll? Is there a way to disable this? Is something else going on?
Note: I set clearsContextBeforeDrawing to NOo n my UIView and this helped somewhat but it's still much slow than I would expect
The layers should not be redrawn, but they will be composited using the GPU. Try using the Sampler and OpenGL ES instruments in Instruments to see if your bottleneck is in the CPU or GPU. You can log extra detail within the OpenGL ES instrument by tapping on the "i" next to the instrument name and selecting the extra parameters (Tiler Utilization, Renderer Utilization, etc.). If one of the OpenGL values is near 100%, then it's the compositing that's holding you back. However, if the sampler is showing a number of calls to -drawRect: or similar methods, then your layers are being redrawn (for some reason) and that could be the problem.
Additionally, you can use the Core Animation instrument to color non-opaque layers, which can lower your display framerate.
Set the needsDisplayOnBoundsChange property on your layers to NO:
[layer setNeedsDisplayOnBoundsChange:NO];
Related
Currently I have a horizontal UIScrollView populated with UIViews. The views are dequeued when moving off screen and reused. Each UIView is subclassed to draw images using drawInRect with [someView setNeedsDisplay] being called when the view enters the screen.
[_image drawInRect:imageRect];
My gradients, text, shapes, etc all load smoothly but as soon as I draw an image I suffer a noticeable performance hit. The size of the image doesn't seem to matter because the scroll view always lags. (I get the same result when using a UIImageView as well.) All of my images are loaded beforehand too.
Is there a better way to draw images that won't result in poor performance?
If you're just drawing images, you might consider using CALayers with CGImageRef contents—drawRect: overrides are going to be slower than CALayer compositing. Another thing to watch out for is alpha-blended images, which require frequent recompositing, or images that aren't aligned to integral pixels. The Improving Image Drawing Performance on iOS tech note is a great place to start on this kind of thing. Once you have the basics in hand, the Core Animation instrument in Instruments will be of great help to you—it watches out for blending, copied images, misaligned images, and screen updates.
I have an EAGLView which I am resizing in an animation, with the animation driven by an NSTimer that calls a draw function. As I resize the EAGLView, I need to adjust the projection for the view to maintain the aspect ratio of the contents. The draw function looks like this:
gameView.frame = newFrame;
[gameView setFramebuffer];
[self updateProjection];
/* Drawing to the EAGLView, gameView, here, then... */
[gameView presentFramebuffer];
Run like this, though, the contents of the EAGLView appear to "stair-step" down the animation. When I record it and look at it frame by frame, it's clear that the projection is adjusted, then the view is resized a very short time later (less than one animation frame).
I suspect that the reason is that the change in the frame of gameView is being deferred, and that in the meantime the updated framebuffer is making its way to the screen. I've tried using CATransactions to get the frame update to take effect immediately, but as I kind of expected for a UIView change, that did nothing. I suppose I could modify the viewport and leave the EAGLView full-frame, but I worry that that might just leave me with synchronization issues elsewhere (say, with updates to any overlaid CALayers).
Does this seem like a reasonable assessment of the problem? How can I prevent it -- that is, how can I best ensure that the framebuffer presentation coincides with the change in the actual frame of the EAGLView (and other CA elements)?
Thanks!
I encountered a similar issue with a custom EAGLView I wrote. The problem was that when changing orientation, the view got resized and the contents stretched, and only after the animation, the scene with the right proportions was displayed.
I think yours is the same problem, and I don't think you can force CoreAnimation to wait for your view to update before rendering an animation frame, because of the way it works (CA isn't aware of the drawing mechanism of the view or the layer on which operates).
But you can easily bypass the problem: open you xib file containing the view, switch to the attributes inspector on the right. Select your EAGLView (the same applies to GLKView), and under the section View there's the Mode attribute. If you're creating the view programmatically, set the contentModeproperty.
This value describes how the view contents are managed during an animation. Until the buffer is displayed, the old scene gets resized according to that mode; perhaps you can find a mode that fits the animation you need to achieve.
If you're worried about proportions, the center content mode may work, but the part of scene that hasn't been rendered yet will appear empty during the animation.
Unfortunately the redraw mode won't necessarely make your EAGLView refresh: the view is actually redrawn, but potentially with the old contents of the color buffer. It's a problem of getting the buffer filled at the right time.
You could try to resize and prerender a frame big enough to cover the whole animation before starting it; or you could try to render a frame to an UIImage, replace the view on the fly, animate it, the place the EAGLView back, but this may seriously affect performance.
The way I solved my issue was making simply the EAGLView big enough.
Regarding the blending of other CALayers, as far as I tried, I encountered no synchronization issue; the EAGLView updates when it can, and the other layers too. Just set the right properties to CAEAGLLayer if you're using alpha channel, and rememeber that blending the layers every time OpenGL updates your scene may be expensive in terms of performance.
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.
I created UIScrollView and added UIView with lots of tiled UIButtons in the UIView. My problem is, when every time I zoom out the content using zoomToRect method of UIScrollView to the minimum scale I set, the zooming out is not smooth. But zoom-in and zoom-out for the second time is smooth. How can I make the zooming out for the first time to smooth zooming?
Thanks.
The slow initial zoom is obviously due to the phone allocating all the UIButtons the first time it has to draw them. They should be allocated incrementally or before the user starts to interact with them.
What are you doing that requires so much loading and drawing? It doesn't sound like the user would be able to interact with the million or so buttons they might be viewing.
I would suggest adding a pile of code to a UIScrollView Sub Class that makes it aware of it's content size, and it can then init the required UIButtons before the user starts to interact with your UIScrollview, or incrementally as I said.
There is demo code called 'Tiling' that sheds some light on using UIScrollViews to manage large content. It's quite complex, but a very complete demo that I'm sure most projects implement if they handle UIScrollViews with tiled content.
Before zoom out set your UIScrollView* scrollView like:
scrollView.contentInset = UIEdgeInsetsMake(0,0,0,0);
The documentation says that the clipsToBounds property of UIView will clip the drawing to the bounds, or more precisely that the subView can't draw outside of the bounds of the superView.
Sounds nice, but what does that mean in practice?
If I set that to YES, then my subView will automatically only draw those parts which are not outside the bounds of the superView. so it increases the overall performance or do I still have to make sure that I don't create any views that are not visible, i.e. inside a UIScrollView ?
I think it's the opposite: turning on clipping hurts performance because it has to set up a clipping mask. I vaguely remember reading this somewhere, but I can't find it now.
The use case for clipsToBounds is more for subviews which are partially outside the main view. For example, I have a (circular) subview on the edge of its parent (rectangular) UIView. If you set clipsToBounds to YES, only half the circle/subview will be shown. If set to NO, the whole circle will show up. Just encountered this so wanted to share.
The (possible) performance hit is only deterministic if you know the view hierarchy. As mentioned above, usually the renderer will use GPU cycles to draw the view UNLESS some view within the hierarchy uses drawRect:. This does not affect OpenGL ES application because drawRect:is omitted in this type of apps.
From my understanding, determining and displaying the clipped area may take less cycles than actually calculating/drawing/blending the whole view. As of OpenGL ES 2.0 clipping can be calculated in GPU.