Offscreen bitmap drawing from background thread

Discussion in 'iOS Programming' started by bredell, Apr 27, 2010.

  1. bredell macrumors regular

    Joined:
    Mar 30, 2008
    Location:
    Uppsala, Sweden
    #1
    Hi!

    I have a scrollview containing about 100 smaller views, all the same size as the screen. My problem is that when my app wants to show the scrollview it takes about 5 seconds to create the contents of all those smaller views which makes the app freeze for 5 seconds.

    To fix this I've decided to add the views to the scrollview one at a time and start with the area that's visible in the scrollview. To make this as smooth as possible I want to start a background thread that draws the contents of the view into an offscreen bitmap, when the bitmap is finished the main thread adds the view to the scrollview and the view's drawRect: method simply paints the already completed bitmap into the view.

    It kinda works. Most of the time. But occasionally it fails, the offscreen drawing returns "invalid context" or it freezes completely. This is how I do the offscreen drawing:

    Code:
    ...
    [self performSelectorInBackground:@selector(drawBitmap) withObject:nil];
    ...
    
    - (void)drawBitmap {
    	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    	int part = nextBitmapPart;
    	CGRect rect = CGRectMake(0, partSize.height * part, partSize.width, partSize.height);
    
    	UIGraphicsBeginImageContext(partSize);
    	CGContextRef ctx = UIGraphicsGetCurrentContext();
    	CGContextTranslateCTM(ctx, 0, -partSize.height * part);
    	[self drawPart:rect context:ctx];
    	bitmapImage = [UIGraphicsGetImageFromCurrentImageContext() retain];
    	UIGraphicsEndImageContext();
    	bitmapPart = part;
    	[self performSelectorOnMainThread:@selector(showNextPart) withObject:nil waitUntilDone:NO];
    	[pool release];
    }
    
    - (void)drawPart:(CGRect)rect context:(CGContextRef)ctx {
    	
    	CGContextSetFillColorWithColor(ctx, cgBgColor);
    	CGContextFillRect(ctx, rect);
    ...
    
    	for (int i = 0; i < nWords; i++) {
    		if (CGRectIntersectsRect(rect, words[i].textRect)) {
    			NSString *text = [NSString stringWithUTF8String:words[i].text];
    			[text drawAtPoint:words[i].textRect.origin withFont:fonts[words[i].font].font];
    		}
    	}
    }
    I have removed a lot of the code in the listing above.

    When it freezes I get the following traceback:

    Code:
    #0	0x94294342 in semaphore_wait_signal_trap
    #1	0x94299e06 in pthread_mutex_lock
    #2	0x0193d533 in CGFontCacheLock
    #3	0x0193d499 in CGGlyphLockLockGlyphBitmaps
    #4	0x03f15b3f in ripc_RenderGlyphs
    #5	0x03f239e4 in ripc_DrawGlyphs
    #6	0x0193c2f4 in draw_glyphs
    #7	0x0193bb3f in CGContextShowGlyphsWithAdvances
    #8	0x026458d8 in WebCore::Font::drawGlyphs
    #9	0x02645505 in WebCore::Font::drawGlyphBuffer
    #10	0x0264522a in WebCore::Font::drawSimpleText
    #11	0x02644ec0 in drawAtPoint
    There seems to be a collision with some resource dealing with fonts. I've done a few tests and it seems that the problems only occurs when I do the offscreen drawing from an alternate thread and someone else tries to do UI drawing from the main thread at the same time. But I'm not 100% sure that's the case.

    I know that all UI operations have to be done from the main thread, but this shouldn't include drawing to an offscreen bitmap, I hope.

    I previously tried using the CGBitmapContextCreate() function but I couldn't get the drawing of text to work at all so I switched to using UIGraphicsBeingImageContext() instead.

    Any suggestions?
     
  2. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #2
    Why shouldn't it? You can't make up the rules here.

    Stop using UI* functions/methods in a background thread and figure out why it wasn't working with your CGBitmapContext.

    Also, if you optimize your scroll view's content view to draw on demand only (e.g. its visible rect), you can make it pretty fast and possibly avoid threading altogether. Also look into CGLayer.
     
  3. firewood macrumors 604

    Joined:
    Jul 29, 2003
    Location:
    Silicon Valley
    #3
    Check whether the NSString drawing methods can only be used in the main UI thread.

    Try drawing the text using a CG call instead.
     
  4. bredell thread starter macrumors regular

    Joined:
    Mar 30, 2008
    Location:
    Uppsala, Sweden
    #4
    I've had a hard time trying to figure out exactly what is forbidden to do from a background thread. I had the impression that it was only the actual updating of the screen that was forbidden, but I guess I should avoid all of the UI* classes.

    I've also started looking into the CATiledLayer and CAScrollLayer.

    The NSString drawing methods are UIKit additions so they might not work from a background thread. The problem with the low level CG calls is that they, as far as I know, don't support unicode until iPhone OS 3.2.
     
  5. bredell thread starter macrumors regular

    Joined:
    Mar 30, 2008
    Location:
    Uppsala, Sweden
    #5
    I've now confirmed that the NSString drawing methods can't be used from a background thread. This means that if you need to draw Unicode characters there's currently no way of doing that from a background thread.
     
  6. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #6
    Have you tried CGContextShowTextAtPoint()?

    I think the 3.2 SDK added the CoreText API which is basically what's being used behind the scenes.
     
  7. bredell thread starter macrumors regular

    Joined:
    Mar 30, 2008
    Location:
    Uppsala, Sweden
    #7
    The CG* functions only support the Mac Roman character set, if you want to use Unicode you can only draw arrays of glyphs and the function that converts from text to glyphs is missing. It's been added in iPhone OS 3.2.
     
  8. firewood macrumors 604

    Joined:
    Jul 29, 2003
    Location:
    Silicon Valley
    #8
    Rendering text alone is usually fast enough for the foreground UI thread. Is there a reason you can't send a performSelectorOnMainThread message from the background thread for any text drawing stuff needed after all the slow drawing stuff is done?
     
  9. bredell thread starter macrumors regular

    Joined:
    Mar 30, 2008
    Location:
    Uppsala, Sweden
    #9
    I've tried that, drawing text into one small part at a time and calling myself with performSelector. The problem is that the app freezes until all parts have been drawn completely.

    But I've now realized that I'm probably doing all of this the wrong way. So far I've tried drawing the entire contents and then using a UIScrollView to show only a part of it. I should instead try to draw only the visible part and ignore the rest, and keep drawing new parts when the user is scrolling, and remove the parts that are no longer visible. But I don't know how to do this, looking into the UIScrollView delegate protocol I can't see anything that will let me know when the user is about to scroll into a new part of the scrollable content. But surely this must be possible, that's the way UITableView does it.
     
  10. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #10
  11. bredell thread starter macrumors regular

    Joined:
    Mar 30, 2008
    Location:
    Uppsala, Sweden
    #11
    Oh yes, I've studied the UIScrollView delegate documentation. But the scrollViewWillBeginDragging: method only tells me that the user has started scrolling, it doesn't give me any information about which part of my content view is about to become visible. It doesn't tell me the scrolling direction.

    But I've solved it now. It turns out that the scrollViewDidScroll: method is called constantly during the scrolling operation. I originally thought it was only called when scrolling had ended. So, now I've implemented everything using only two UIViews that I move around as the user is scrolling. The two views are the same size as the visible region of the scrolling view and as soon as the user scrolls to an uncovered area of the content view, one of my views is moved into place and updated through a drawRect: call.
     

Share This Page