1

I'm looking to draw to the screen, pixel by pixel. I've got this code working but its slow... I find this very very odd as the same kind of code written in javascript is super fast. fast enough that I has to slow down my interval that calling this...

What do I need to do to be able to speed this up by at least 2 or 3 times?

objective-c

- (void)drawPixelAtX:(int)X atY:(int)Y { [NSBezierPath fillRect:NSMakeRect(X, self.frame.size.height - Y, 1, 1)]; } - (void)drawCharater:(int)charCode atX:(int)charX atY:(int)charY color:(NSColor*)color; { if (charCode >= [self.font count]) return; [color set]; NSArray *template = [self.font objectAtIndex:charCode]; for (int y = 0; y < 16; y++) { for (int x = 0; x < 8; x++) { if ([[template objectAtIndex:y*8+x] intValue] == 1) [self drawPixelAtX:(charX * 8 + x) atY:(charY * 16 + y)+1]; } } } - (void)drawRect:(NSRect)dirtyRect { [NSGraphicsContext saveGraphicsState]; for (int x=0; x < 80; x++) { for (int y=0; y < 25; y++) { AnsiScreenChar *c = [self.screen objectAtIndex:(y*80)+x]; [c.bgColor set]; [NSBezierPath fillRect:NSMakeRect(x*8, self.frame.size.height - ((y+1)*16), 8, 16)]; [self drawCharater:c.data atX:x atY:y color:c.fgColor]; } } [NSGraphicsContext restoreGraphicsState]; } 

javascript

function updateDisplay() { var $this = $(this); var data = $this.data('ansi'); for (var i = 0, length1 = data.screen.length; i < length1; ++i) { var a = data.screen[i]; // cache object for (var j = 0, length2 = a.length; j < length2; ++j) { if (a[j][4]) { data.ctx.save(); data.ctx.beginPath(); data.ctx.rect(data.fontWidth * j, data.fontHeight * i, data.fontWidth, data.fontHeight); data.ctx.clip(); data.ctx.fillStyle = a[j][0]; data.ctx.fillRect(data.fontWidth * j, data.fontHeight * i, data.fontWidth, data.fontHeight); data.ctx.fillStyle = a[j][1]; drawCharater.call(this, a[j][2], [j, i], 1); data.ctx.restore(); a[j][4] = false; } } } } 

Profileing info

Running Time Self Symbol Name 12371.0ms 21.8% 12371.0 objc_msgSend 2612.0ms 4.6% 2612.0 CFNumberGetValue 2446.0ms 4.3% 2446.0 _class_getInstanceSize 1910.0ms 3.3% 1910.0 -[__NSArrayI objectAtIndex:] 1482.0ms 2.6% 1482.0 _CFExecutableLinkedOnOrAfter 1384.0ms 2.4% 1384.0 object_getIndexedIvars 1350.0ms 2.3% 1350.0 -[AnsiView drawCharater:atX:atY:color:] 1277.0ms 2.2% 1277.0 OSAtomicCompareAndSwap64Barrier$VARIANT$mp 1270.0ms 2.2% 1270.0 ripl_BltShape 1261.0ms 2.2% 1261.0 -[__NSCFNumber intValue] 1185.0ms 2.0% 1185.0 CFRelease 1047.0ms 1.8% 1047.0 CGSShmemGuardUnlock 1005.0ms 1.7% 1005.0 ripr_Rectangles 

I take it CFNumberGetValue is dealing with the intValue call?

3 Answers 3

4

Invoking the fillRect method of NSBezierPath to draw each and every pixel is crazy. If you must draw things a single pixel at a time, then draw into your own buffer, and when it's ready to display, then you send it into OS X's highly vector-oriented hardware-accelerated OpenGL-based graphics system. If you're not going to take advantage of any of the advanced drawing primitives offered by Quartz, then don't make a million calls to Quartz per frame. If you don't want to implement the blitting yourself, use SDL or Allegro. But you should really consider whether every letter needs to be drawn pixel-by-pixel. OS X's graphics system is very good at compositing images, as that is its primary purpose. And by the way, why can't you just use normal text-rendering methods and bundle in your own font file?

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

3 Comments

I've tried finding exact fonts, can can't. The fonts I have found, when displayed, get distorted and are not 100% pixel perfect. I would rather not deal with 3rd part libs at all for this. I originally was mucking with a NSBitmapImageRep and updating the data directly (not setPixel.) That was 100 times slower than this.
It's possible to get pixel-accurate font rendering in OS X. The Proggy fonts work just fine in Terminal and other apps. If you can't find a font that suits your needs, use FontForge.
@user57368 can you include an example or link on how to use/bundle custom fonts?
3

The first thing I would do is profile your code, and see what takes so much time. My guess is that a lot of time is spent here:

[[template objectAtIndex:y*8+x] intValue] 

If my guess is right, you should be able to gain some speed by switching template from NSArray to a regular C array of integers.

EDIT:

Now that I see the profile, I think my guess was right. Did you try switching to C arrays?

If you are open to a different approach to rendering fonts, see this article on texture atlases, it may give you some nice ideas.

3 Comments

this is a better article on doing texture atlases with calayer IMHO mysterycoconut.com/blog/2011/01/cag1
@slf It's funny how that article links to the one I posted (in fact, that's how I found my link).
This did speed things up a little but not enough. I'm going to try user57368's method. I'm marking this correct as its the only one that sped up the current algorithm.
1

I strongly agree that this method of drawing is insanity. Chances are there is a better way to do this. Toggling the individual pixels of a bitmap then calling a single bind texture and a single draw is the first thing that comes to mind.

Assuming you will not or can not change this algorithm, from your profiling info it looks like you are spending all your time sending messages. The first thing I would do to speed it up is pull it all into a single ugly monster method instead of breaking it out into three. Yes it's ugly and stupid, but it would effectively illuminate all the message sends inside the loop and perform only the logic.

EDIT:

Drawing to a bitmap context without objc_messageSend ...

CGContextRef MyCreateBitmapContext (int pixelsWide, int pixelsHigh) { CGContextRef context = NULL; CGColorSpaceRef colorSpace; void * bitmapData; int bitmapByteCount; int bitmapBytesPerRow; bitmapBytesPerRow = (pixelsWide * 4);// 1 bitmapByteCount = (bitmapBytesPerRow * pixelsHigh); colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);// 2 bitmapData = calloc( bitmapByteCount );// 3 if (bitmapData == NULL) { fprintf (stderr, "Memory not allocated!"); return NULL; } context = CGBitmapContextCreate (bitmapData,// 4 pixelsWide, pixelsHigh, 8, // bits per component bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast); if (context== NULL) { free (bitmapData);// 5 fprintf (stderr, "Context not created!"); return NULL; } CGColorSpaceRelease( colorSpace );// 6 return context;// 7 } void DrawMyStuff() { CGRect myBoundingBox;// 1 myBoundingBox = CGRectMake (0, 0, myWidth, myHeight);// 2 myBitmapContext = MyCreateBitmapContext (400, 300);// 3 // ********** Your drawing code here ********** // 4 CGContextSetRGBFillColor (myBitmapContext, 1, 0, 0, 1); CGContextFillRect (myBitmapContext, CGRectMake (0, 0, 200, 100 )); CGContextSetRGBFillColor (myBitmapContext, 0, 0, 1, .5); CGContextFillRect (myBitmapContext, CGRectMake (0, 0, 100, 200 )); myImage = CGBitmapContextCreateImage (myBitmapContext);// 5 CGContextDrawImage(myContext, myBoundingBox, myImage);// 6 char *bitmapData = CGBitmapContextGetData(myBitmapContext); // 7 CGContextRelease (myBitmapContext);// 8 if (bitmapData) free(bitmapData); // 9 CGImageRelease(myImage); } 

explanation of what it's all doing in the Quartz2D guide

2 Comments

do you have an example URL or tutorial or something? I don't mind changing this to something better but like I said I tried messing with a NSBitmapImageRep an is was slower....
I'll try to find a good example of what I'm talking about, did you try merging them into one monolithic method without any improvement?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.