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

tobeythorn

macrumors newbie
Original poster
Jun 30, 2008
6
0
I've been trying to write a simple subclass of NSOpenGLView. However, the view never redraws unless I resize the window containing it. This has been driving me crazy for about a week and I've tried everything I can think of. My drawRect in my NSOpenGLView is below. I've set double buffer for the view in IB.

Code:
-(void) drawRect: (NSRect) rect	{
	NSLog(@"%@", @"Draw Rect");
	if(rootNode == nil)	{
		rootNode = [ self createScene ];
	}
	
	[[self openGLContext] makeCurrentContext];
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glClearColor(0, 0, 0, 0);
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	
	id<NSObject> myRenderer = [ [ Renderer alloc ] init ];
	[ myRenderer renderTree: rootNode ];

	[ [ self openGLContext ] flushBuffer ];
}

My createScene method simply creates a scenegraph "node" that contains VBO's to make a triangle. [myRenderer renderTree: rootNode] just draws the node:

Code:
-(void) renderLeaf: (id<NSObject>) leaf	{
		glPushMatrix();

		glTranslatef(((float) rand())/(RAND_MAX), 0.5f, 0.0f);
		glColor3f(((float) rand())/(RAND_MAX), 0.06f, 0.35f);
	
		glBindBufferARB(GL_ARRAY_BUFFER_ARB, [ leaf vboIDv ]);
		glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, [ leaf vboIDp ]);
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, 0);

		glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
		glDisableClientState(GL_VERTEX_ARRAY);

		// bind with 0, so, switch back to normal pointer operation
		glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
		glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
				
		glPopMatrix();
		
	}

Cocoa is supposed to be so easy, but it's starting to seems like it just obscures things that shouldn't be obscured, and without thorough explanation in the API.

Anyway, many thanks if you can give me some suggestions to fix this problem.

-Tobey
 
It might be down to the way you initialised your view.

Don't know if this helps but this is how I initialise my view:-

Code:
- (id) initWithFrame: (NSRect) frame
{
	GLuint attribs[] = 
	{
		NSOpenGLPFANoRecovery,
		NSOpenGLPFAWindow,
		NSOpenGLPFAAccelerated,
		NSOpenGLPFADoubleBuffer,
		NSOpenGLPFAColorSize, 24,
		NSOpenGLPFAAlphaSize, 8,
		NSOpenGLPFADepthSize, 24,
		NSOpenGLPFAStencilSize, 8,
		NSOpenGLPFAAccumSize, 0,
		0
	} ;

	NSOpenGLPixelFormat* fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes: (NSOpenGLPixelFormatAttribute*) attribs]; 
	
	if (!fmt)
		NSLog(@"No OpenGL pixel format");

	return self = [super initWithFrame:frame pixelFormat: [fmt autorelease]];
}
b e n
 
Ben,
I tried initializing like you suggested, but drawRect is still only happening once.
 
Well, everything I've read says that you just need to let the view know it needs updating at the end of drawRect ([ [ self openGLContext ] flushBuffer ];). I don't want to use a timer because I don't want locked framerates. Also, where would this timer go? InitWithFrame?
 
All that [ [ self openGLContext ] flushBuffer ] does is updates your view by copying the backbuffer to the front. It doesn't trigger another redraw on your view. It's up to you to force a redraw. You might want to do this when the scene has changed in some way, or you may want to do it on a regular basis using a timer. You can force a redraw by sending the view a setNeedsDisplay message, eg

Code:
[ self setNeedsDisplay: YES ] ;

If you decide to add a timer then you would need something like this:-

Code:
- (void) drawRect: (NSRect) rect
{
	if ( ! animationTimer )
		animationTimer=[ [ NSTimer scheduledTimerWithTimeInterval:0.017 target:self selector:@selector(animationTimerFired:) userInfo:nil repeats:YES ] retain ] ;
....
....
....
}

/**
 * Service the animation timer.
 */
- (void) animationTimerFired: (NSTimer *) timer
{
	[ self setNeedsDisplay: YES ] ;
}

animationTimer would be an instance variable of your NSOpenGLView derived view class.

b e n
 
Ben,
That's the thing, I've tried placing setNeedsDisplay at the end of my drawrect method, but with no success. It draws once fine, but unless i resize the window, or i awake it, it won't redraw.
 
SOLVED:
I decided to strip things down and make a non-openGL custom NSView to see if it would update correctly. It did not. After many many hours reading Apples documentation, i found this and this.


Basically, the window should automatically redraw itself, but doesn't work right. I have no idea why. So instead, since I want to control updates manually in my game anyway, I do [ myWindow setAutodisplay:NO ]. However, it is still not enough to manually display the view. The containing window must be manually displayed afterwards.

CONCLUSION: Apple needs to emphasize that the window needs to be updated, and not just its views in it's documentation. Apple also needs to update it's deprecated openGL examples and realize too that NSTimers are not suited for every application and provide some examples without. Lastly, Apple guides are good.
 
View only redraws on resize...

I went through a similar problem, that also frustrated me for weeks, but had a different solution:

Just make the OpenGLView single buffered.
I tried it, and OS-X seems to have enough built in buffering that I see no flickering or tearing.

P.S. Yeah, some of the OpenGL examples are out of date enough to crash the machine. But this is less a documentation issue, and more a failure on Apple's part to provide backwards compatibility.
 
Setting needsDisplay to YES within the drawRect method would be terribly inefficient. Cocoa probably safeguards against this practice.

Basically it works like this: when you change any variables that influence drawRect, you also need to set needsDisplay. If you are rotating a cube, then you need to set needsDisplay in the same place that you change the orientation variable. If something happens when you press X, then put setNeedsDisplay into your "X" code.

It is common to set up a timer which sets needsDisplay 60 times per second for example, but it is not necessary.
 
Still something's fishy in all this...

Setting needsDisplay to YES within the drawRect method would be terribly inefficient. Cocoa probably safeguards against this practice.

Basically it works like this: when you change any variables that influence drawRect, you also need to set needsDisplay. If you are rotating a cube, then you need to set needsDisplay in the same place that you change the orientation variable. If something happens when you press X, then put setNeedsDisplay into your "X" code.

It is common to set up a timer which sets needsDisplay 60 times per second for example, but it is not necessary.

The behavior with double buffering confirms that setting needsDisplay of the OpenGLView is incomplete and causes some undesired side effects. Whether by design or a bug, it seems that only by telling the parent window of the OpenGLView to update will the full redraw of the view take place. Numerous programmers have noticed this problem, which is most apparent when trying to embed OpenGL into a Cocoa Gui app...
 
I have another solution

I had the same problem as listed above. Any (NSOpenGLView or Custom) view is drawn once and then, it is not drawn again until the window containing it is resized or moved. It turned out, the double buffer was actually used but never swapped. In my case, all the solutions provided here did not succeeded. BUT! Now after approximately 20 hours of trial and error, search and despair, I found the solution. It's fairly simple. Add the following to your implementation if you have an NSOpenGLView subclass:

Code:
- (void) prepareOpenGL
{
  GLint swapInt = 1;
  [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; // set to vbl sync
}

When using a custom NSView subclass just set this value right after the creation of the context. It looks like this swap interval is 0 by default since the new sdk.

Hope this helps. Found this thread in the first place while searchich for a solution.
 
This link seems to be about getting a function call every v sync and then triggering a render.


NSOpenGLView does not update on it's own and there is no setting to make it update automatically. The best way is to use a display link to sync with the refresh rate of the display.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.