On iOS, what is the fastest way to cache a drawn screen image and display it?
Asked Answered
B

2

9

Instead of letting drawRect redraw thousands of point every time, I think there are several ways to "cache the image on screen" and any additional drawing, we will add to that image, and just show that image when it is time to drawRect:

  1. Use BitmapContext and draw to a bitmap, and in drawRect, draw this bitmap.

  2. Use CGLayer and draw the CGLayer in drawRect, and this may be faster than method 1, as this image is cached in the graphics card (and it will not count towards the RAM usage for the "memory warning" on iOS?)

  3. Draw to a CGImage, and use the view's layer: view.layer.contents = (id) cgimage;

So there seems to be three methods, and I think CALayer in method (3) can only use a CGImage to achieve it. CALayer by itself cannot cache a screen image, not like CGLayer in (2).

Is method (2) the fastest out of all three, and are there other methods that can accomplish this? I actually plan to animate a few screen images, (looping over 5 or 6 of them), and will try using CADisplayLink to try a highest frame rate of 60fps. Will any of method (1), (2), or (3) use the memory in graphics card and therefore not use the RAM and therefore less likely to get a memory warning from iOS too?

Bedew answered 27/5, 2012 at 18:36 Comment(7)
Why don't you try it and see which one is the fastest in your particular situation, instead of asking 4 different questions about the same topic?Rhamnaceous
@Kurt thanks for your previous insights. as of right now, I am just starting out on iOS, and sometimes I am not sure about the actual correct implementation and how to measure the drawing speed and memory usage... I may try one implementation, mess up all the existing code, and then find out that it is not a preferred solution...Bedew
You have to write your own code sometime. You'll never learn unless you make your own mistakes. We cannot do your thinking for you.Rhamnaceous
sure, I may try (3) as well and see how it stacks against (2)... although (3) actually seems quite similar to (1)... I also wanted to ask questions to clear up about any concepts, such as CALayer not being able to cache anything... or maybe it can? By using 5 sublayers and 5 graphics contexts, and draw to each one, and remove all sublayers except one...Bedew
but maybe a sublayer cannot be used as a cache if it is ever added as part of the layer hierarchy, because if it is part of the hierarchy, then it is subject to redraw from scratch... is this a correct concept?Bedew
Brad Larson has excellent advice here, but as you're a beginner you really should start with the basics and worry about these kinds of small optimizations later. The iOS drawing system is very efficient and does almost all the caching you could imagine for you automatically. You shouldn't try to short-circuit that unless you really know what you're doing. Learn UIKit and learn simple Core Graphics before you worry about bypassing drawRect:. You're almost certain to build something slower than the built-in system.Protrusile
@Rob thanks guys... (by the way I just got your book a few days ago, Rob.) At this point, I am looking for a way that doesn't need to do bitmap within the memory space but can more closely use the GPU itself, as doing it in the RAM space might be slower?Bedew
F
12

Based on the last several questions you've asked, it looks like you are completely confusing CGLayers and CALayers. They are different concepts, and are not really related to one another. A CGLayer is a Core Graphics construct which aids in the rendering of content repeatedly within the canvas of a Core Graphics context, and is confined within a single view, bitmap, or PDF context. Rarely have I had the need to work with a CGLayer.

A CALayer is a Core Animation layer, and there is one backing every UIView within iOS (and layer-backed NSViews on the Mac). You deal with these all the time on iOS, because they are a fundamental piece of the UI architecture. Each UIView is effectively a lightweight wrapper around a CALayer, and each CALayer in turn is effectively a wrapper around a textured quad on the GPU.

When displaying a UIView onscreen, the very first time content needs to be rendered (or when a complete redraw is triggered) Core Graphics is used to take your lines, arcs, and other vector drawing (sometimes including raster bitmaps, as well) and rasterize them to a bitmap. This bitmap is then uploaded and cached on the GPU via your CALayer.

For changes in the interface, such as views being moved around, rotated, scaled, etc., these views or layers do not need to be redrawn again, which is an expensive process. Instead, they are just transformed on the GPU and composited in their new location. This is what enables the smooth animation and scrolling seen throughout the iOS interface.

Therefore, you'll want to avoid using Core Graphics to redraw anything if you want to have the best performance. Cache what parts of the scene you can within CALayers or UIViews. Think about how older-style animation used cels to contain portions of the scene that they would move, instead of having animators redraw every single change in the scene.

You can easily get hundreds of CALayers to animate about the screen smoothly on modern iOS devices. However, if you want to do thousands of points for something like a particle system, you're going to be better served by moving to OpenGL ES for that and rendering using GL_POINTS. That will take much more code to set up, but it may be the only way to get acceptable performance for the "thousands of points" you ask about.

Feisty answered 27/5, 2012 at 21:47 Comment(2)
So you mean use either CALayer or UIView to cache the pre-drawn image? If using UIView, do you mean doing a [[UIView alloc] init], and set its bounds, and then draw inside it using CGContextFillRect(context, CGRectMake(x, y, 1, 1));, and when inside drawRect, just show the pre-drawn image from that UIView object? Or, if using CALayer, what do you draw to? Do you draw into a CGImage and use self.layer.contents = (id) cgimage; to show it in drawRect? So you mean using UIView or CALayer will probably be faster than CGLayer?Bedew
(the CGLayer method as described in developer.apple.com/library/mac/#documentation/graphicsimaging/… , for the comment "If you must draw to a buffer, use a layer instead of a bitmap graphics context.", "a graphics context associated with a video card might cache the layer on the video card, which makes drawing the content that’s in a layer much faster than rendering a similar image that’s constructed from a bitmap graphics context.")Bedew
B
5

One fast method that allows both caching of graphics and modification of those cached graphics contents is a mash-up of your methods (1) and (3).

(1) Create your own bitmap-backed graphics context, draw to it, and later modify it at any time (incrementally add one point or thousands of points every now and then, etc.) as needed. Unfortunately it will be invisible, because there is no way to directly get any bitmap to the display on an iOS device.

So, in addition,

(3) at some frame rate (60 Hz, 30 Hz, etc.), if the bitmap is dirty (has been modified), convert the bitmap context into a CGImage and assign that image to the contents of a CALayer. That will convert and copy your entire bitmap's memory to the GPUs texture cache (this is the slow part). Then use core animation to do whatever you want with the layer (flush it, composite it, fly it around the window, etc.) to display the texture made out of your bitmap. Behind the scenes, Core animation will eventally let the GPU throw a quad using that texture onto some composited window tiles which will eventually be sent to the devices display (this description probably leaving out a whole bunch of stages in the graphics and GPU pipelines). Rinse and repeat as needed in the main UI run loop. My blog post on this method is here.

There is no way to partially modify the contents of an existing GPU texture in use. You either have to replace it with a complete new texture upload, or composite another layer on top of the texture's layer. So you will end up keeping double the memory in use, some in the CPUs address space, some in the GPU texture cache.

Burnett answered 28/5, 2012 at 6:28 Comment(3)
is method (2) not as fast? Is CALayer more likely cached on GPU than CGLayer?Bedew
iOS devices do not have graphics cards.Burnett
I thought it is built into the chip.... A4, A5, A6, and each says its graphics power is N times better than the previous one...Bedew

© 2022 - 2024 — McMap. All rights reserved.