2

I recently came across a situation where I have to draw many 2D images to the screen. Sometimes about 200 small images have to be drawn. I used to call the UIImage.draw(in:) method for that purpose, but quickly learned (through profiling) that it is rather slow. I could speed things up by making the images smaller, i.e. use the right size for the image, and not images that are way too big and which have to be scaled down (this is something that I would have done later anyways, but during development you wanna try things out quickly.) This speeded it up from about 400ms to 100ms. Still too slow. I could also make it faster by only drawing the parts of the screen that actually change. This helps, except in the case that I have to draw most of the screen. It's still way to slow: I mean, many modern 3D games can draw >10000 triangles per frame, with HD textures and 60 frames per second, on an iPhone SE; so I'm pretty sure there is a way to draw 100 images to the screen on an iPhone 7 in under 1 millisecond.

I thought about rewriting the view in OpenGL ES, Metal, or maybe using SpriteKit, but I wonder if this may be overkill.

For the game I am developing, I have at most 15 different images that I need to draw multiple times each. At most 1000 images are drawn at once. The maximum resolution of an image is 120*120 pixels.

What is a sufficiently fast way to draw images to screen? It would be okay to have a setup phase that takes maybe 300ms and which would copy all the image data into graphic memory, or something like that. A fast drop-in-replacement of UIImage would be the most convenient though.

1
  • Maybe use CALayer instances to display the images using the layer.contents property. The layer is only rendered once and from there on things should be pretty fast. Commented Feb 17, 2018 at 21:56

1 Answer 1

5

As you have already found out it is not very fast to draw UIImage objects yourself. I had myself a similar requirement in a previous project, and did various tests to try to find an efficient enough method to get fairly small bitmaps on screen. While in no way claiming to know the full truth on the subject, I think I have same interesting pieces of information to share.

CALayer + contents
What I found was (as Palle says in his brief comment) that CALayer with the bitmap set to its contents property was the fastest way to do it. Probably not nearly as fast as metal or OpenGL, but magnitudes faster than draw. It seems that when you add a layer to the screen it ends up in the GPU memory, so repositioning such layers on (or off) screen is very fast. You can also do transforms (in 3D) on these layers with little penalty. Also, as I understand it, using the same bitmap as contents for many layers will actually share memory on the GPU as well.
If you haven't worked with layers before it's fairly straight forward. On iOS every UIView is backed by a CALayer, in practice it means that you can easily get to a layer by just doing view.layer.

Creating a bitmap backed layer is easy, just instantiate it and set the contents, then add it to your view controller's layer (or wherever you want it).

CALayer *layer = [CALayer layer]; layer.contents = [UIImage imageNamed:@"yourImage"].CGImage; [self.view.layer addSublayer:layer]; 

A final note on working with layers. By default all CALayer objects have animations attached to them for various property changes. This is handy if you, for example, want to animate a move of an object, because all you have to do is to set the new position. However, this might not be what you want if you are doing your own scenery, and perhaps animating with CADisplayLink. In such case you must remove these default animations, or they will interfere with your updates.

Remove implicit animations from a CALayer objects

- (void)removeAnimationFromLayer:(CALayer *)layer { layer.actions = @{ @"contents": [NSNull null], @"position": [NSNull null], @"frame": [NSNull null], @"opacity": [NSNull null], @"bounds": [NSNull null], @"affineTransform": [NSNull null], @"sublayerTransform": [NSNull null], @"transform": [NSNull null], @"zPosition": [NSNull null], @"anchorPoint": [NSNull null], @"cornerRadius": [NSNull null], @"sublayers": [NSNull null], @"onLayout": [NSNull null], }; } 

UIImageView
Almost as fast as layers are (perhaps surprisingly) UIImageView objects when used correctly. They too, seem to be highly optimized and very fast to move around on screen. My experience is that you should try not to re-assign the image on the UIImageView, but rather use separate UIImageView objects for different images. As with layers, you should try to only do positioning and transforms on the objects to maintain speed.
A notable difference between UIImageView and CALayer is that CALayer is for output only, while UIImageView, as being a descendant of UIView and UIResponder, also handles input. This can be worth the trade-off if you need it. Another difference is that UIImageView usually is a bit easier to work with.

I recommend you do a few test of your own to see what performance you get. UIImageView might get you all the way!

CALayer contents property

Sign up to request clarification or add additional context in comments.

2 Comments

It worked: I used the UIImageView method and it is many times faster.
Used CALayer, big speed improvement. I suspect UIImageView just wrapping CALayer so it's a UIView child and fits into the UIView hierarchy and system (handling input, etc.).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.