Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

ArtOfWarfare

macrumors G3
Original poster
Nov 26, 2007
9,558
6,058
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.
 
Last edited:

ArtOfWarfare

macrumors G3
Original poster
Nov 26, 2007
9,558
6,058
dumb question, have you tried :

Code:
		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.
 

KnightWRX

macrumors Pentium
Jan 28, 2009
15,046
4
Quebec, Canada
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

macrumors G3
Original poster
Nov 26, 2007
9,558
6,058
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

Moderator
Staff member
Aug 9, 2009
10,740
8,416
A sea of green
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

macrumors Pentium
Jan 28, 2009
15,046
4
Quebec, Canada
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 ?
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.