We have a game engine of sorts, developed under 3.2. It did use NSTimer to fire 
off the game loop, but now uses CADisplayLink, at 15fps. The game loop 
basically sends messages to as many as 15 sprites, telling them to update their 
position and their frame. A sprite is in essence a CALayer, and we use 
-[CALayer setContents:] and -[CALayer setPosition:] to update those properties. 
All sprites are updated in a CATransaction, and everything is performed on the 
main thread. The image used for contents is obtained from a UIImage 
spriteAtlas. The atlas can be quite large: 1000x1600. It contains all the 
animation frames for a sprite. We grab the appropriate image portion from the 
spriteAtlas using CGImageCreateWithImageInRect(CGImageRef image, CGRect rect). 
In general, we haven't been hitting the memory limit on the device with this 
approach, and when it does, it recovers nicely.

Now this all worked perfectly in 3.2, but in iOS > 4, we have performance 
problems, on the device only. Basically the sprites are very laggy and the 
animation is choppy, until resources have fully loaded (it can take from 1s to 
30s to fully settle down). The culprit would appear to be -[CALayer 
setContents:], which can take on the order of 0.3s-0.8s, the first time the 
image is used. The problem is then exacerbated as we re-enter the gameloop, 
with resources not in place.

So to solve this, we have tried several approaches (apart from waiting for a 
possibly never-coming performance fix to -[CALayer setContents:]):

1. Preload image assets. One would think this would be easy, but nothing seems 
to preload a CGImageRef until it is actually painted on the screen. Or at the 
very least, there is no time penalty until you try to paint an image on the 
screen. Nothing we have tried has improved this situation. The docs for 
-[UIImage CGImage] state: "If the image data has been purged because of memory 
constraints, invoking this method forces that data to be loaded back into 
memory. Reloading the image data may incur a performance penalty." I had 
assumed this to mean that the image will be fully loaded into memory, but our 
results would suggest that it doesn't actually get loaded until it is used (eg 
in setContents:). We're using +[UIImage imageNamed:] to load the image, and so 
far the cache inherent to that method has been good to us.

2. Invoke -[CALayer setContents:] on a background thread. This does not change 
the situation at all. I suspect that -[CALayer setContents:] simply grabs the 
main thread and does its work there, regardless of the thread it is invoked 
from, though I can't find any docs that say this explicitly.

[spriteLayer performSelectorInBackground:@selector(setContents:) 
withObject:(id)frameImage];
vs
[spriteLayer setContents:(id)frameImage];

3. Block until the image has been loaded. Basically, we skip any updates on the 
sprite until a flag appears, indicating the image has been loaded. Since the 
image loading is asynchronous, AFAICT, we just set the flag before and after 
-[CALayer setContents:].

Again, this does not improve performance, which says to me that setContents on 
other instances of CALayer on the screen are being affected, even though their 
imageContents are loaded and cached. I can only imagine this is because 
everything is being invoked on the same thread, on the same runloop.


One final note - my CADisplayLink timer does not seem very reliable - ie, I get 
between 2 and 200 fps, when I should only ever get 15fps, assuming it is tied 
directly to hardware. Perhaps because it is also on the main runloop, it is 
suffering as well? While these anomalies are rare, they occur exactly when 
trying to load a new sprite atlas image. It seems like this alone could be 
causing the problem. I'm looking now at running it on another runloop or 
thread. NSTimer doesn't change anything appreciably, either.

        timer_ = [CADisplayLink displayLinkWithTarget:self 
selector:@selector(gameloop)];
        [timer_ setFrameInterval:4]; // 15fps
        [timer_ addToRunLoop:[NSRunLoop currentRunLoop] 
forMode:NSDefaultRunLoopMode];

I measure fps like this in the gameloop:

        double startTime = CACurrentMediaTime(); 
        double fps = 1/(startTime - lastTime_);
        lastTime_ = startTime;
        ILog (@"fps: %f", fps);

So I feel like I'm flailing around a bit here now. I know what the problem is, 
but have been unable to find an approach that will improve the situation. It 
may be that we have to split up our sprite atlases, but I wanted to ask for 
advice from the list first. 

Any thoughts or suggestions would be greatly appreciated! Thanks,

Julian




_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to