PDA

View Full Version : Core Graphics Lagging?




ArtOfWarfare
Mar 6, 2012, 07:27 PM
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.

- (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.



KnightWRX
Mar 6, 2012, 09:06 PM
dumb question, have you tried :

if(![self needsDisplay]) [self setNeedsDisplay:YES];

ArtOfWarfare
Mar 6, 2012, 10:45 PM
dumb question, have you tried :

if(![self needsDisplay]) [self setNeedsDisplay:YES];

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.

MorphingDragon
Mar 7, 2012, 02:21 AM
Don't you need to close your vector path in Quartz2D?

KnightWRX
Mar 7, 2012, 04:14 AM
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.

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.

ArtOfWarfare
Mar 7, 2012, 09:42 AM
Don't you need to close your vector path in Quartz2D?

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


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

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.

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

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...)

chown33
Mar 7, 2012, 10:30 AM
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.

KnightWRX
Mar 7, 2012, 02:57 PM
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.

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