1

I'm using the QuartzImage class in one of the demo projects and what I'm was trying to achieve was a simple frame display unit that basically draws an image (320x480) every 1/10th of sec. So my "frame rate" should be 10 frames per sec.

In the QuartzImage demo class, there is a drawInContext method and in this method, it's basically drawing a CGImageRef using the CGContextDrawImage(), I measured the time it took to finish complete and it's taking on average around ~200ms.

2011-03-24 11:12:33.350 QuartzDemo[3159:207] drawInContext took 0.19105 secs

-(void)drawInContext:(CGContextRef)context
{
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    CGRect imageRect;
    imageRect.origin = CGPointMake(0.0, 0.0);
    imageRect.size = CGSizeMake(320.0f, 480.0f);
    CGContextDrawImage(context, imageRect, image);
    CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
    NSLog(@"drawInContext took %2.5f secs", end - start);
}

Can anyone explain why it's taking that long and if there is any other way of improving the performance? 200ms just seems much more longer than it should have taken.

UPDATES I tried @Brad-Larson's suggestion but not seeing a lot of performance improvement.

So the updated version is I got my own class

@interface FDisplay : UIView {
    CALayer *imgFrame;
    NSInteger frameNum;
}
end

So in my Class implementation

- (id)initWithFrame:(CGRect)frame {
    ............

    frameNum = 0;
    NSString *file = [NSString stringWithFormat:@"frame%d",frameNum];
    UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType:@"jpg"]];
    imgFrame = [CALayer layer];
    CGFloat     nativeWidth = CGImageGetWidth(img.CGImage);
    CGFloat     nativeHeight = CGImageGetHeight(img.CGImage);
    CGRect      startFrame = CGRectMake(0.0, 0.0, nativeWidth, nativeHeight);
    imgFrame.contents = (id)img.CGImage;
    imgFrame.frame = startFrame;
    CALayer *l = [self layer];
    [l addSublayer:imgFrame];
}

I have a NSTimer going at 0.1f calling my refresh method

NSString *file = [NSString stringWithFormat:@"frame%d",frameNum];
UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType:@"jpg"]];
frameNum++;
if (frameNum>100) 
    frameNum = 0;

[CATransaction begin]; 
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 
[imgFrame setContents:(id)img.CGImage];
[CATransaction commit];
end = CFAbsoluteTimeGetCurrent();
NSLog(@"(%d)refresh took %2.5f secs", [[self subviews] count],end - start);

I think I got everything right but the frame rate is still way low,

refresh took 0.15960 secs

2 Answers2

2

Using Quartz to draw out images is about the slowest way you could do this, due to the way that content is presented to the screen.

As a step up, you could use a UIImageView and swap out its hosting image for each frame. An even faster approach might be to use a CALayer and set its contents property to a CGImageRef representing each image frame. You may need to disable implicit animations using a CATransaction for this to be as fast as it can be. If I recall, I was able to get 320x480 images to be drawn at over 15 FPS on an original iPhone using the latter method.

Finally, for optimal performance you could set up an OpenGL ES display with a rectangle that filled the screen and supply the images as textures to be rendered on that rectangle. This would require significantly more code, but it would be extremely fast.

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • @Brad-Larson Is 15 FPS on an iPhone4 and new generation devices? The test I ran was using an old 3G iPhone. – Objective-Dude Mar 24 '11 at 19:18
  • @Objective-Dude - As I state, the 15+ FPS was on an original model iPhone (before iPhone 3G). The newer devices post-iPhone 3G S are much, much faster of course. – Brad Larson Mar 24 '11 at 19:24
  • @Brad-Larson Thanks, I missed that for some reason. And disabling implicit animations, you mean doing this [link]http://stackoverflow.com/questions/2244147/disabling-implicit-animations-in-calayer-setneedsdisplayinrect/2244734#2244734[/link] in this answer? – Objective-Dude Mar 24 '11 at 19:32
  • @Objective-Dude - Yes, that's one way. The other would be to wrap any changes to `contents` with `[CATransaction begin]; [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];` and `[CATransaction commit];`. The way you link to is probably better for this application. – Brad Larson Mar 24 '11 at 19:40
  • @Brad-Larson Tried your suggestion above but still seeing ~150ms, I updated the question to reflect the updated code, I think I have it right but could use look at it.. Thanks! – Objective-Dude Mar 24 '11 at 20:36
  • 1
    @Objective-Dude - Loading each one of these images from disk will be a slow operation, as will the conversion of a UIImage to a CGImageRef. Either of those could be slowing things down, instead of your actual drawing. Try loading a direct CGImage from your JPEGs on disk, as described here: http://stackoverflow.com/questions/3550201/how-exactly-to-make-a-cgimageref-from-an-image-on-disk/3550683#3550683 . Also, use the Time Profiler instrument to find your exact hotspot in this code. – Brad Larson Mar 24 '11 at 21:13
  • @Brad-Larson Loading an UIImage the way I'm doing is pretty fast actually, each image loads at ~15ms. Can you share your implementation of how you actually got 15FPS? – Objective-Dude Mar 24 '11 at 23:57
  • @Objective-Dude - However, the conversion to a CGImageRef from a UIImage could still be slow, and your timing could be missing some of the hidden cost of instantiating a UIImage from disk. Did you try the direct CGImage loading approach I pointed out? – Brad Larson Mar 25 '11 at 15:01
  • @Objective-Dude - The code for one such approach to presenting a bunch of images onscreen with good performance can be found at the link Mo points to in his answer here: http://stackoverflow.com/questions/442076/method-for-animating-images-like-a-movie-on-iphone-without-using-mpmovieplayer/529761#529761 – Brad Larson Mar 25 '11 at 15:03
  • @Brad-Larson Mo's example does run at ~15 fps, the strange thing is using my own set of jpg files, it's only capable of doing ~8fps (the frame rate ) that I've been seeing all this time. Loading CGImageRef and setting layer contents of the UIImageView did not see any difference too. I'm really puzzled why 2 different set of jpgs are rendered at different speeds? – Objective-Dude Mar 26 '11 at 03:34
  • @Objective-Dude - Mo's example uses PNGs, not JPEGs. PNG loading is optimized on iOS, so it's much faster than JPEG loading. PNGs are also crushed when you compile them, so they are further tweaked by that process. Again, I'd recommend running the Time Profiler instrument against your application, which will reveal where the true performance bottleneck is here. Charge any functions and libraries to their callers that you don't recognize, and I think you'll see that the image loading is the slowest part of your application. – Brad Larson Mar 26 '11 at 14:12
0

I have been porting my game from Android to iPhone and I was shocked by the performance of Quartz. The same code on Android is 10x faster than on iPhone. I have a few DrawImages, and a bunch of line draws and beziers. It's so slow on iOS, specially iPhone 4, where the processor struggles to keep up with the retina display resolution. My game performed perfectly at 60fps in almost ANY android device.

I tried several approaches for rendering (always avoiding OpenGL). I started by drawing everything at every frame. Then I started rendering as much as I could before the game loop, by using UIImage's. Now I'm trying to go with CALayers. Although I can see the game at steady 60FPS on iPhone 3GS and 4S, the most I can get on the iPhone 4 is 45 FPS.

Pedro Soares
  • 1,117
  • 12
  • 14