Justin808
Justin808

Reputation: 21512

Objective-C graphics drawing speed

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?

Upvotes: 1

Views: 680

Answers (3)

slf
slf

Reputation: 22767

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

Upvotes: 1

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726579

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.

Upvotes: 3

user57368
user57368

Reputation: 5765

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?

Upvotes: 4

Related Questions