1. Welcome to the new MacRumors forums. See our announcement and read our FAQ

Core Graphics Lagging?

Discussion in 'Mac Programming' started by ArtOfWarfare, Mar 6, 2012.

  1. ArtOfWarfare, Mar 6, 2012
    Last edited: Mar 6, 2012

    macrumors 603

    ArtOfWarfare

    #1
    I'm trying to make an app that simply draws a line on a window that reflects the position of two touches on a trackpad. Here's the code:

    Oh, quick note, startPoint and endPoint are both CGPoint iVars. This is more just me experimenting with NSTouches and Core Graphics, so I'm not too concerned with following proper practices and not having iVars.

    Code:
    - (void)drawRect:(NSRect)rect
    {
        CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];
        CGContextSetLineCap(myContext, kCGLineCapSquare);
        CGContextSetRGBStrokeColor(myContext, 0.0, 0.0, 0.0, 1.0);
        CGContextSetLineWidth(myContext, 1.0);
        CGContextMoveToPoint(myContext, startPoint.x + 0.5, startPoint.y + 0.5);
        CGContextAddLineToPoint(myContext, endPoint.x + 0.5, endPoint.y + 0.5);
        CGContextStrokePath(myContext);
    }
    
    - (void)touchesMovedWithEvent:(NSEvent *)event
    {
    	NSLog(@"Touches moved.");
    	NSMutableSet *touches = [[event touchesMatchingPhase:NSTouchPhaseAny inView:self] mutableCopy];
    	NSTouch *firstTouch = (NSTouch *)[touches anyObject];
    	[touches removeObject:firstTouch];
    	NSTouch *secondTouch = (NSTouch *)[touches anyObject];
    	
    	if (secondTouch)
    	{
    		float startX = (firstTouch.normalizedPosition.x)*(self.frame.size.width);
    		float startY = (firstTouch.normalizedPosition.y)*(self.frame.size.height);
    		startPoint = CGPointMake(startX, startY);
    		float endX = (secondTouch.normalizedPosition.x)*(self.frame.size.width);
    		float endY = (secondTouch.normalizedPosition.y)*(self.frame.size.height);
    		endPoint = CGPointMake(endX, endY);
    		
    		[self setNeedsDisplay:YES];
    	}
    }
    If the user moves their fingers quickly enough, the line will fall behind. If the user moves their fingers quickly enough for long enough, they can actually lift their fingers up and watch the line to continue to move for several seconds after the user has stopped.

    Suggestions?

    Edit: I'm running this in the debugger in Xcode on an Original MacBook Air (1.6 GHz). Even so, it shouldn't matter... I'd rather if it aborted drawing anything it missed in-between and skipped to what it should look like right now.
     
  2. macrumors Pentium

    KnightWRX

    #2
    dumb question, have you tried :

    Code:
    		if(![self needsDisplay]) [self setNeedsDisplay:YES];
     
  3. macrumors 603

    ArtOfWarfare

    #3
    I tried; it didn't seem to change anything.

    Something I discovered, however, is that the lag seems proportional to the view's size. If it's 480x360, there isn't any noticeable lag. Scale the view up to 1280x800 (fullscreen) and it's very noticeable.

    Is this a limit of the CPU/GPU - I don't think this computer even has a GPU - ? It just seems... silly. All I'm trying to do is draw a single line*, it doesn't seem like it should go so slow...

    *A single line several dozen times a second.
     
  4. macrumors 603

    MorphingDragon

    #4
    Don't you need to close your vector path in Quartz2D?
     
  5. macrumors Pentium

    KnightWRX

    #5
    have you tried setNeedsDisplayInRect to limit the area you have to redraw ?

    If your computer has no GPU, you're not seeing anything on screen. The GPU is a new fangled term for graphics card.
     
  6. macrumors 603

    ArtOfWarfare

    #6
    I'm not sure what you're referring to...


    I have tried that now. It performs better, but I'm having a little bit of difficulty in determining where I need to redraw... right now I have it simply redrawing the area where the line is, but I can see lots of partially drawn lines from where the line was. If I simply pick my fingers up and put them down someplace else entirely, I can get multiple full lines drawn. I guess the solution is going to be making sure the old line's rectangle, in addition to the new line's rectangle, is redrawn.

    When I said GPU I was referring to a dedicated GPU, as opposed to an integrated one. (I think... hardware isn't my strong point... working on improving that in school...)
     
  7. macrumors 603

    #7
    Use the timestamp of the event to limit the update rate (more specifically, the interval between events). Also use the distance between touches to limit the update rate.

    If the time between two events is small, and the distance is small, redraw nothing. Heck, you could probably get away with just looking at the distance between event N's position and event N-1's position, regardless of timestamp. Using timestamp, however, gives you better filtering for fine movement. That is, multiple repeated events at the new location will cause the tracking to move to that location, even if the distance is small.

    "Small" simply means below some threshold. You will probably have to experiment to find an optimal value. I suggest starting it fairly high, like 10 or 20 pixels, so you can actually observe the effects of the filtering. Then make it smaller until it feels like a good balance between responsive (fast) and accurate (tracks fine movement).

    The goal here is to avoid redraws. So every time you call setNeedsDisplay:YES, you are forcing a redraw, whether the content to be drawn really needs it or not. Figure out a way to measure your redraw rate, and show it to you, and then use that metric to work on the spatial/temporal filtering (distance and timestamp differences).


    Oh, and I'd bet cash money that if you take out the NSLog, it will be markedly faster. Because adding NSLog's to a real-time process is a recipe for real-time failure.
     
  8. macrumors Pentium

    KnightWRX

    #8
    Could you also use [[NSRunLoop mainLoop] cancelPerformSelector:target:argument:] to delete the drawRect call from the main loop before issuing another redraw command ?
     

Share This Page