PDA

View Full Version : Best way to update NSView for animation


AsLan^
2005-12-23, 04:20
I have a problem doing animation with Cocoa and I cant find a good way to solve it.

Basically, I've got a custom NSView subclassed and I've put my NSImage:compositeToPoint method in the drawRect method of the NSView.

I then use another object which creates two NSTimers, one to refresh the NSView by calling its display method and another to change the coordinates that the NSImage:compositeToPoint method uses to draw my bitmap (a png in this case).

My problem is that this just doesn't seem to work right, the animation is jerky and slow, and changing the NSTimer intervals doesnt noticably speed things up or make the animation smoother.

I've googled the problem and I see that others have had problems with NSTimers too, and one guy has a write up about it which I dont really understand over at cocoadevcenter. Also, I would like to avoid using NSAnimation so I can keep compatibility with 10.3.

So... any ideas ?

AsLan^
2005-12-23, 05:27
I found another tutorial over at macdevcenter that uses NSDate objects inside NSThreads instead of NSTimers.

I've implemented their examples into my code and there seems to be some performance gain but I'm not sure. Was this the best method for animation prior to NSAnimation ?

Update: I still think something is wrong, when I use this technique and increase the number of bimaps to five, I start to get stutter and screen flicker. This cant be right, I have previously used a very similar method to draw bitmaps in java and I can easily manipulate 100+ png images in the window (called from a vector during each window redraw no less) without stutter or screen flicker.


#import "CardTable.h"

@implementation CardTable

- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];

spider = [NSImage imageNamed:@"Spider4"];

[NSThread detachNewThreadSelector:@selector(animate:) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(moveSpider:) toTarget:self withObject:nil];

xPosition = 0;
yPosition = 0;

return self;

}

- (void)drawRect:(NSRect)rect
{

int i;

for (i = 0; i < 5; i++)
{
[spider compositeToPoint:NSMakePoint(xPosition + (i * 100),
yPosition + (i * 100)) operation:NSCompositeSourceAtop];
}

}

- (void)setXPosition:(int)x
{
xPosition = x;
}

- (void)setYPosition:(int)y
{
yPosition = y;
}

- (void)animate:(id)anObject
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSDate *date = nil;

double framerate = 3333.0 / 1000000.0;

while (YES)
{
[self setNeedsDisplay:YES];
if (date != nil)
[date release];
date = [NSDate dateWithTimeIntervalSinceNow:framerate];
[NSThread sleepUntilDate:date];
}

[pool release];
[NSThread exit];
}

- (void)moveSpider:(id)anObject
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSDate *date = nil;

BOOL goRight = TRUE;

while (YES)
{
if (goRight && xPosition > 400)
{
goRight = FALSE;
}

if (! goRight && xPosition < 0)
{
goRight = TRUE;
}


if (goRight)
{
xPosition = xPosition + 1;
}
else
{
xPosition = xPosition - 1;
}

if (date != nil)
[date release];

date = [NSDate dateWithTimeIntervalSinceNow:0.005];
[NSThread sleepUntilDate:date];
}

[pool release];
[NSThread exit];
}


@end

AsLan^
2005-12-24, 04:11
Perhaps someone knows another technique for animating things in OS X ?

I'm open to suggestions :)

AsLan^
2005-12-24, 05:07
It turns out that the poor performance comes from redrawing the whole window with every refresh. It should be only redrawing the areas of the screen where something has changed.

When I work out how to do that... I'll let you know :)

Enki
2005-12-24, 13:55
OpenGL via an NSOpenGLView, but that requires a whole new area of expertise. It does go straight to the heart of how OS X draws everything though, so most higher-layered OS related slowdowns are avoided. Even though OS X does have some OpenGL performance issues at times, accessing the standard directly will be faster than accessing it through several extra layers of OS abstraction.

spotcatbug
2005-12-24, 19:26
Yeah, OpenGL was my thought, too. I think, if we "bit the bullet" and used OpenGL (just for 2D), it would actually end up being easier in the end. And, as an aside, we may be able to slip some cool 3D-style effects into the mix without hardly any extra effort. But, like I said on the Wiki, I'm won't push for the 3D stuff.

Enki
2005-12-24, 21:34
And remember invoking OpenGL sends the hard work off to the GPU explicitly. No guessing needed as to which of the Cocoa API's have OpenGL primitive acceleration.

AsLan^
2005-12-27, 00:12
Why not bump another thread :)

It turns out the method you need is called [self setNeedsDisplayInRect:aRect] and then you pass it a rectangle with the same coords as the area you are drawing in. You can easily make the Rect with the NSMakeRect(x, y, w, l) function which comes in quite handy.

So my final code, for anyone who was intested looked like this...


#import "CardTable.h"

@implementation CardTable

- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];

spider = [NSImage imageNamed:@"Spider4"];
background = [NSImage imageNamed:@"TigerPuzzle"];

[NSThread detachNewThreadSelector:@selector(animate:) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(moveSpider:) toTarget:self withObject:nil];

int i;

for (i = 0; i < 4; i++)
{
xPosition[i] = 0 + (i * 160);
yPosition[i] = 400 ;//- (i * 120);
}

return self;

}

- (void)drawRect:(NSRect)rect
{

[background drawInRect:NSMakeRect(0, 0, 640, 480)
fromRect:NSMakeRect(0, 0, 360, 360)
operation:NSCompositeSourceAtop
fraction:1];

int i;

for (i = 0; i < 4; i++)
{
[spider compositeToPoint:NSMakePoint(xPosition[i], yPosition[i])
operation:NSCompositeSourceAtop];

}

}

- (void)animate:(id)anObject
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSDate *date = nil;

double framerate = (3333.0 / 1000000.0) / 2;

while (YES)
{
int i;
for (i = 0; i < 4; i++)
{
[self setNeedsDisplayInRect:NSMakeRect(xPosition[i], yPosition[i], 180, 120)];
}

if (date != nil)
[date release];
date = [NSDate dateWithTimeIntervalSinceNow:framerate];
[NSThread sleepUntilDate:date];
}

[pool release];
[NSThread exit];
}

- (void)moveSpider:(id)anObject
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSDate *date = nil;

BOOL jumpUp[4];

int i; for (i = 0; i < 4; i++)
{
jumpUp[i] = TRUE;
}

while (YES)
{


int i;

for (i = 0; i < 4; i++)
{
if (jumpUp[i] && yPosition[i] > 360)
{
jumpUp[i] = FALSE;
}

if (! jumpUp[i] && yPosition[i] < 0)
{
jumpUp[i] = TRUE;
}

if (i == 0 || i == 2)
{
if (jumpUp[i])
{
yPosition[i] = yPosition[i] + (0.5);
}
else
{
yPosition[i] = yPosition[i] - (2 - (i / 2));
}
}
else
{
if (jumpUp[i])
{
yPosition[i] = yPosition[i] + ((i / 2) + 0.5);
}
else
{
yPosition[i] = yPosition[i] - (0.5);
}
}

}

if (date != nil)
[date release];

date = [NSDate dateWithTimeIntervalSinceNow:0.005];
[NSThread sleepUntilDate:date];
}

[pool release];
[NSThread exit];
}


@end


And it all animates quite nicely :)