iPhone 4 MSAA Test Results

Okay, I thought I’d start a discussion of MSAA, considering that the 3GS and above have the hardware support for it (and Apple has implemented a software solution for earlier OS4 devices). Also, with 2xMSAA considered low cost on the SGX chipset, it has spurred me to start tinkering :).

Attached is the result, sorry for the large image size. This is on a iPhone 4 using Unity 3.0 with a simple scene:

  • 1 x Cube (with rotation component)
  • 1 x Direction light
  • 1 x Camera (with MSAA Bind component)
  • XCode modifications to support MSAA

XCode example code here:
http://jamstudiosgames.com/publications/multisampling.pdf

(I’m not affiliated with these people)

I’m hoping to do some real world performance tests with what I have now.

OK but what about framerate? :smile:

That’s awesome! What is the performance impact? How are you going about reducing sample size for lower-end devices like 1G and 2G? Is is possible to turn off AA for those devices through Unity or Xcode?

Can you provide a basic outline of how you implemented this in Xcode? I’m trying to follow that slide presentation, but it doesn’t say which files to put the code in. Specifically I need to know:

  1. Where does the Render Buffer code go?
  2. Where does the MSAA Buffer code go?
  3. Where does the Draw Scene code go?

this looks great, I’d intrigued to see more of your experiments on this! Epic Citadel (UDK) uses MSAA on iOS 4 and it looks great

In the case of 1G and 2G, I guess you would do the usual Unity thing and check the device generation and set the sample size/enabled/disabled status that way. I already have support for this, so you can do it dynamically. There is definitely a performance impact. Fragment shader runs once for the each pixel, but then it is sampled 4x (in the case of 4xMSAA). I’ll post some numbers (internal profiler) and code when I get a chance. This will only work on the pro version also. It does work on 3G but is software only and slow.

Tommy.

Please post an example of how you’re integrating the buffer code. I’d very much like to try it but have no clue where to start.

Also, is this a setting we can let players toggle at runtime? That would be a great option, so players can decide for themselves if the performance hit is worth it.

For sure… I’ll do it tonight.

Yep absolutely.

Looks sweet :slight_smile:

Not really.
The iphone is all about a fixed platform and the best user experience.
Either your game is fast enough to still be playable with it active or it isn’t. There is no case where it is fast enough for user X but not for user Y.
You can check that directly on your end during testing and be assured that it holds for all as there is no second different configuration of the iphone4 :slight_smile:

As such I wouldn’t make it an option to the user, but an option thats enabled / disabled basing on the generation you run on

Yes- we are looking to implement as well and though your PDF spelled out the code it didnt say where we should be implementing.

Looking forward to your outline :smile:

I would also like to know how you are implementing the code as well. Please post when you get a chance

I haven’t had a chance to look at the code again since I posted here… I’m super busy. There’s probably issues with it, so you are warned :slight_smile: You must implement a [__internal] hook in the c#/js layer in Unity3d and call the MSAA enabled to toggle the msaa and call the MSAA bind function before your camera renders using OnPreRender(). This code goes into your AppController, or somewhere else more suitable. Here are the relevant functions:

bool msaaEnabled = false;
CGSize globalSize;

struct MyEAGLSurface
{
	GLuint		format;
	GLuint		depthFormat;
	GLuint		framebuffer;
	GLuint		renderbuffer;
	GLuint		msaaFrameBuffer;	
	GLuint		msaaRenderBuffer;	
	GLuint		msaaDepthBuffer;	
	GLuint		depthBuffer;	
	CGSize		size; 
};

extern "C" void MSAA_Enabled( bool enabled )
{	
	if( enabled  !msaaEnabled )
	{
		// Create MSAA buffers! 
		glGenFramebuffersOES(1, _surface.msaaFrameBuffer ); 
		glGenRenderbuffersOES(1, _surface.msaaRenderBuffer );
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, _surface.msaaFrameBuffer ); 
		glBindRenderbufferOES(GL_RENDERBUFFER_OES, _surface.msaaRenderBuffer );
		
		// Samples is the amount of pixels the MSAA buffer uses to make one pixel on the render 
		// buffer. Use a small number like 2 for the 3G and below and 4 or more for newer models
		int samples = 4;
		glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER_OES, samples, GL_RGB5_A1_OES, globalSize.width, globalSize.height);
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, _surface.msaaRenderBuffer);
		
		// MSAA Depth buffer
		glGenRenderbuffersOES(1, _surface.msaaDepthBuffer); 
		glBindRenderbufferOES(GL_RENDERBUFFER_OES, _surface.msaaDepthBuffer);
		glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER_OES, samples, _surface.depthFormat, globalSize.width, globalSize.height);
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, _surface.msaaDepthBuffer);
	}
	else 
	{
	}
	
	msaaEnabled = enabled;
}

extern "C" void MSAA_BindTarget()
{
	if( _surface.msaaFrameBuffer  msaaEnabled )
	{
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, _surface.msaaFrameBuffer); //Bind MSAA
	}	
}

bool CreateWindowSurface(EAGLView *view, GLuint format, GLuint depthFormat, bool retained, MyEAGLSurface* surface)
{
	CGSize newSize;
	GLuint oldRenderbuffer;
	GLuint oldFramebuffer;
	
    CAEAGLLayer* eaglLayer = (CAEAGLLayer*)[view layer];
	
	surface->format = format;
	surface->depthFormat = depthFormat;
	
	surface->msaaFrameBuffer = 0;
	surface->msaaRenderBuffer = 0;
	surface->msaaDepthBuffer = 0;
	surface->depthBuffer = 0;
	surface->renderbuffer = 0;
	surface->framebuffer = 0;
	
	eaglLayer.opaque = YES;
	eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
									[NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
	
	newSize = [eaglLayer bounds].size;
	newSize.width = roundf(newSize.width);
	newSize.height = roundf(newSize.height);
	
#ifdef __IPHONE_4_0
    int resolution = UnityGetTargetResolution();
    
    if (resolution == kTargetResolutionNative ||
        resolution == kTargetResolutionHD)
    {
        if ([view respondsToSelector:@selector(setContentScaleFactor:)])
        {
            UIScreen* mainScreen = [UIScreen mainScreen];
            CGFloat scaleFactor = mainScreen.scale;
            [view setContentScaleFactor:scaleFactor];
            newSize.width = roundf(newSize.width * scaleFactor);
            newSize.height = roundf(newSize.height * scaleFactor);
            UnitySetInputScaleFactor(scaleFactor);
        }
    }
	
	
#endif
	globalSize = newSize;
	
	glGetIntegerv(GL_RENDERBUFFER_BINDING_OES, (GLint *) &oldRenderbuffer);
	glGetIntegerv(GL_FRAMEBUFFER_BINDING_OES, (GLint *) &oldFramebuffer);
	
	// Create the main frame and render buffers
	glGenFramebuffersOES(1, &surface->framebuffer); 
	glGenRenderbuffersOES(1, &surface->renderbuffer);
	
	// Bind the frame and render buffer
	glBindFramebufferOES(GL_FRAMEBUFFER_OES, surface->framebuffer); 
	glBindRenderbufferOES(GL_RENDERBUFFER_OES, surface->renderbuffer);
	
	// Set storage for render buffer
	if(![_context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:eaglLayer]) 
	{
		glDeleteRenderbuffersOES(1, &surface->renderbuffer);
		glBindRenderbufferOES(GL_RENDERBUFFER_BINDING_OES, oldRenderbuffer);
		return false;
	}
	
	// Attach the renderbuffer
	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, surface->renderbuffer);
		
	if (depthFormat) 
	{		
		// Regular depth buffer
		glGenRenderbuffersOES(1, &surface->depthBuffer); 
		glBindRenderbufferOES(GL_RENDERBUFFER_OES, surface->depthBuffer);
		glRenderbufferStorageOES(GL_RENDERBUFFER_OES, surface->depthFormat, newSize.width, newSize.height);
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, surface->depthBuffer);
	}
	
	surface->size = newSize;
	glBindRenderbufferOES(GL_RENDERBUFFER_OES, oldRenderbuffer);
	
	CHECK_GL_ERROR();	
	
	return true;
}

void DestroySurface(MyEAGLSurface* surface)
{
	EAGLContext *oldContext = [EAGLContext currentContext];
	
	if (oldContext != _context)
		[EAGLContext setCurrentContext:_context];
	
	if(surface->msaaDepthBuffer) 
	{
		glDeleteRenderbuffersOES(1, &surface->msaaDepthBuffer);
		surface->msaaDepthBuffer = 0;
	}
	
	if(surface->depthBuffer) 
	{
		glDeleteRenderbuffersOES(1, &surface->depthBuffer);
		surface->depthBuffer = 0;
	}
	
	glDeleteRenderbuffersOES(1, &surface->msaaRenderBuffer);
	surface->msaaRenderBuffer = 0;
	
	glDeleteFramebuffersOES(1, &surface->msaaFrameBuffer);
	surface->msaaFrameBuffer = 0;
	
	glDeleteRenderbuffersOES(1, &surface->renderbuffer);
	surface->renderbuffer = 0;
	
	glDeleteFramebuffersOES(1, &surface->framebuffer);
	surface->framebuffer = 0;
	
	if (oldContext != _context)
		[EAGLContext setCurrentContext:oldContext];
}

void PresentSurface(MyEAGLSurface surface)
{
	EAGLContext *oldContext = [EAGLContext currentContext];
	GLuint oldRenderbuffer;
	
	if (oldContext != _context)
		[EAGLContext setCurrentContext:_context];
	
	CHECK_GL_ERROR();
	
	glGetIntegerv(GL_RENDERBUFFER_BINDING_OES, (GLint *) &oldRenderbuffer);
	
	if( msaaEnabled )
	{
		glBindFramebufferOES(GL_READ_FRAMEBUFFER_APPLE, surface.msaaFrameBuffer); 
		glBindFramebufferOES(GL_DRAW_FRAMEBUFFER_APPLE, surface.framebuffer); 
		
		// Call a resolve to combine buffers 
		glResolveMultisampleFramebufferAPPLE();	
	}
	
	glBindRenderbufferOES(GL_RENDERBUFFER_OES, surface.renderbuffer);
	
	if(![_context presentRenderbuffer:GL_RENDERBUFFER_OES])
		EAGL_ERROR("swap renderbuffer");
	
	if(oldContext != _context)
		[EAGLContext setCurrentContext:oldContext];
}

Thanks for the code.

Anyone know how to do that?

This is the code I used and it compiles fine but it clearly is not working in the game. I commented out the part about the iphone 4 since I wasn’t sure how to define things in that section. Also had some errors when I put the code above in so I had to change a few things (maybe the code is now wrong?). If anyone has any input, it would be appreciated. Using iTouch 3rd gen to test, btw.

bool msaaEnabled = false;
CGSize globalSize; 

struct MyEAGLSurface
{
	GLuint      format;
	GLuint      depthFormat;
	GLuint      framebuffer;
	GLuint      renderbuffer;
	GLuint      msaaFrameBuffer;   
	GLuint      msaaRenderBuffer;   
	GLuint      msaaDepthBuffer;   
	GLuint      depthBuffer;
	CGSize      size; 
};

typedef EAGLContext*	MyEAGLContext;

@interface EAGLView : UIView {}
@end

MyEAGLContext			_context;
MyEAGLSurface			_surface;
UIWindow *				_window;
NSTimer*				_timer;
id						_displayLink;
BOOL					_accelerometerIsActive = NO;

extern "C" void MSAA_Enabled( bool enabled )
{   
	if( enabled  !msaaEnabled )
	{
		// Create MSAA buffers!
		glGenFramebuffersOES(1, _surface.msaaFrameBuffer );
		glGenRenderbuffersOES(1, _surface.msaaRenderBuffer );
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, _surface.msaaFrameBuffer );
		glBindRenderbufferOES(GL_RENDERBUFFER_OES, _surface.msaaRenderBuffer );
		
		// Samples is the amount of pixels the MSAA buffer uses to make one pixel on the render
		// buffer. Use a small number like 2 for the 3G and below and 4 or more for newer models
		int samples = 4;
		glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER_OES, samples, GL_RGB5_A1_OES, globalSize.width, globalSize.height);
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, _surface.msaaRenderBuffer);
		
		// MSAA Depth buffer
		glGenRenderbuffersOES(1, _surface.msaaDepthBuffer);
		glBindRenderbufferOES(GL_RENDERBUFFER_OES, _surface.msaaDepthBuffer);
		glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER_OES, samples, _surface.depthFormat, globalSize.width, globalSize.height);
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, _surface.msaaDepthBuffer);
	}
	else
	{
	}
	
	msaaEnabled = enabled;
}

extern "C" void MSAA_BindTarget()
{
	if( _surface.msaaFrameBuffer  msaaEnabled )
	{
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, _surface.msaaFrameBuffer); //Bind MSAA
	}   
} 

bool CreateWindowSurface(EAGLView *view, GLuint format, GLuint depthFormat, bool retained, MyEAGLSurface* surface)
{
	CGSize newSize;
	GLuint oldRenderbuffer;
	GLuint oldFramebuffer;
	
    CAEAGLLayer* eaglLayer = (CAEAGLLayer*)[view layer];
	
	surface->format = format;
	surface->depthFormat = depthFormat;
	
	surface->msaaFrameBuffer = 0;
	surface->msaaRenderBuffer = 0;
	surface->msaaDepthBuffer = 0;
	surface->depthBuffer = 0;
	surface->renderbuffer = 0;
	surface->framebuffer = 0;
	
	eaglLayer.opaque = YES;
	eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
									[NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
	
	newSize = [eaglLayer bounds].size;
	newSize.width = roundf(newSize.width);
	newSize.height = roundf(newSize.height);
	
/*#ifdef __IPHONE_4_0
    int resolution = UnityGetTargetResolution();
	
    if (resolution == kTargetResolutionNative ||
        resolution == kTargetResolutionHD)
    {
        if ([view respondsToSelector:@selector(setContentScaleFactor:)])
        {
            UIScreen* mainScreen = [UIScreen mainScreen];
            CGFloat scaleFactor = mainScreen.scale;
            [view setContentScaleFactor:scaleFactor];
            newSize.width = roundf(newSize.width * scaleFactor);
            newSize.height = roundf(newSize.height * scaleFactor);
            UnitySetInputScaleFactor(scaleFactor);
        }
    }
	
	
#endif*/
	globalSize = newSize;
	
	glGetIntegerv(GL_RENDERBUFFER_BINDING_OES, (GLint *) &oldRenderbuffer);
	glGetIntegerv(GL_FRAMEBUFFER_BINDING_OES, (GLint *) &oldFramebuffer);
	
	// Create the main frame and render buffers
	glGenFramebuffersOES(1, &surface->framebuffer);
	glGenRenderbuffersOES(1, &surface->renderbuffer);
	
	// Bind the frame and render buffer
	glBindFramebufferOES(GL_FRAMEBUFFER_OES, surface->framebuffer);
	glBindRenderbufferOES(GL_RENDERBUFFER_OES, surface->renderbuffer);
	
	// Set storage for render buffer
	if(![_context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:eaglLayer])
	{
		glDeleteRenderbuffersOES(1, &surface->renderbuffer);
		glBindRenderbufferOES(GL_RENDERBUFFER_BINDING_OES, oldRenderbuffer);
		return false;
	}
	
	// Attach the renderbuffer
	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, surface->renderbuffer);
	
	if (depthFormat)
	{      
		// Regular depth buffer
		glGenRenderbuffersOES(1, &surface->depthBuffer);
		glBindRenderbufferOES(GL_RENDERBUFFER_OES, surface->depthBuffer);
		glRenderbufferStorageOES(GL_RENDERBUFFER_OES, surface->depthFormat, newSize.width, newSize.height);
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, surface->depthBuffer);
	}
	
	surface->size = newSize;
	glBindRenderbufferOES(GL_RENDERBUFFER_OES, oldRenderbuffer);
	
	CHECK_GL_ERROR();   
	
	return true; 
}

void DestroySurface(MyEAGLSurface* surface)
{
	EAGLContext *oldContext = [EAGLContext currentContext];
	
	if (oldContext != _context)
		[EAGLContext setCurrentContext:_context];
	
	if(surface->msaaDepthBuffer)
	{
		glDeleteRenderbuffersOES(1, &surface->msaaDepthBuffer);
		surface->msaaDepthBuffer = 0;
	}
	
	if(surface->depthBuffer)
	{
		glDeleteRenderbuffersOES(1, &surface->depthBuffer);
		surface->depthBuffer = 0;
	}
	
	glDeleteRenderbuffersOES(1, &surface->msaaRenderBuffer);
	surface->msaaRenderBuffer = 0;
	
	glDeleteFramebuffersOES(1, &surface->msaaFrameBuffer);
	surface->msaaFrameBuffer = 0;
	
	glDeleteRenderbuffersOES(1, &surface->renderbuffer);
	surface->renderbuffer = 0;
	
	glDeleteFramebuffersOES(1, &surface->framebuffer);
	surface->framebuffer = 0;
	
	if (oldContext != _context)
		[EAGLContext setCurrentContext:oldContext];
}

void PresentSurface(MyEAGLSurface surface)
{
	EAGLContext *oldContext = [EAGLContext currentContext];
	GLuint oldRenderbuffer;
	
	if (oldContext != _context)
		[EAGLContext setCurrentContext:_context];
	
	CHECK_GL_ERROR();
	
	glGetIntegerv(GL_RENDERBUFFER_BINDING_OES, (GLint *) &oldRenderbuffer);
	
	if( msaaEnabled )
	{
		glBindFramebufferOES(GL_READ_FRAMEBUFFER_APPLE, surface.msaaFrameBuffer);
		glBindFramebufferOES(GL_DRAW_FRAMEBUFFER_APPLE, surface.framebuffer);
		
		// Call a resolve to combine buffers
		glResolveMultisampleFramebufferAPPLE();   
	}
	
	glBindRenderbufferOES(GL_RENDERBUFFER_OES, surface.renderbuffer);
	
	if(![_context presentRenderbuffer:GL_RENDERBUFFER_OES])
		EAGL_ERROR("swap renderbuffer");
	
	if(oldContext != _context)
		[EAGLContext setCurrentContext:oldContext];
}	

void PresentContext_UnityCallback(struct UnityFrameStats const* unityFrameStats)
{	
#if ENABLE_INTERNAL_PROFILER
	_unityFrameStats = *unityFrameStats;
	
	if (_frameId % BLOCK_ON_GPU_EACH_NTH_FRAME == (BLOCK_ON_GPU_EACH_NTH_FRAME-1))
	{
		Prof_Int64 gpuTime0 = mach_absolute_time();
		
#if ENABLE_BLOCK_ON_GPU_PROFILER
		UnityFinishRendering();
#endif
		
		Prof_Int64 gpuTime1 = mach_absolute_time();
		_gpuDelta = gpuTime1 - gpuTime0;
	}
	else
		_gpuDelta = 0;
#endif
	
	
#if ENABLE_INTERNAL_PROFILER
	Prof_Int64 swapTime0 = mach_absolute_time();
#endif
	
	PresentSurface(_surface);
	
#if ENABLE_INTERNAL_PROFILER
	Prof_Int64 vblankTime = mach_absolute_time();
	
	if (_lastVBlankTime < 0) _lastVBlankTime = vblankTime;
	_frameDelta = vblankTime - _lastVBlankTime; _lastVBlankTime = vblankTime;
	
	Prof_Int64 swapTime1 = vblankTime;
	_swapDelta = swapTime1 - swapTime0;
#endif
}

int OpenEAGL_UnityCallback(UIWindow** window, int* screenWidth, int* screenHeight)
{
	CGRect rect = [[UIScreen mainScreen] bounds];
	
	// Create a full-screen window
	_window = [[UIWindow alloc] initWithFrame:rect];
	EAGLView* view = [[EAGLView alloc] initWithFrame:rect];
	[_window addSubview:view];
	
	//CAEAGLLayer* eaglLayer = (CAEAGLLayer*)[view layer];
	_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
	
	MSAA_Enabled(true);
	MSAA_BindTarget();
	
	if (!_context)
		return false;
	
	if (![EAGLContext setCurrentContext:_context]) {
		_context = 0;
		return false;
	}
	
	if (!CreateWindowSurface(view, GL_RGB565_OES, GL_DEPTH_COMPONENT16_OES, NO, _surface)) {
		return false;
	}
	
	glViewport(0, 0, _surface.size.width, _surface.size.height);
	[_window makeKeyAndVisible];
	[view release];
	
	*window = _window;
	*screenWidth = _surface.size.width;
	*screenHeight = _surface.size.height;	
	
	return true;
}

bump

I have attached a sample AppController.mm that does MSAA as well as proper Discarding of buffers. Only tested on iPhone4.

In my sample app here is what perf was like on iPhone 4.
es 1.1, no msaa, 960x640: 60 fps, 30% device util
es 2.0, no msaa, 960x640: 20 fps, 100% device util
es 1.1, msaa 2x, 960x640: 28 fps, 100% device util
es 1.1, msaa 4x, 960x640: 28 fps, 100% device util
es 2.0, msaa 4x, 960x640: 13 fps, 100% device util

396540–13616–$AppController.mm.zip (9.23 KB)

Thanks jimmaysDad, works great

just tried this on iPhone3GS, works awesome. Still getting good fps with es 1.1. Thanks for this!

works great on my itouch 3rd gen but crashes the ipad…anyone have an idea why?

nvm, had the sdk set to 4.1 instead of 3.2

That last Appcontroller has some pretty big issues. Notably the discard was discarding the wrong thing and not discarding the stencil. I bet you it will run MUCH faster with the following changes. The bold parts are the things to change.

#ifdef __IPHONE_4_0
	if (surface.msaaEnabled) {
		// Bind MSAA buffer and resolve before discarding
		glDisable(GL_SCISSOR_TEST);
		glBindFramebufferOES(GL_READ_FRAMEBUFFER_APPLE, surface.msaaFrameBuffer);
		glBindFramebufferOES(GL_DRAW_FRAMEBUFFER_APPLE, surface.framebuffer);
		glResolveMultisampleFramebufferAPPLE();
		
		
		// Discard color and depth only. 
		GLenum discard[] = {GL_COLOR_ATTACHMENT0_OES, GL_DEPTH_ATTACHMENT_OES, [B]GL_STENCIL_ATTACHMENT_OES[/B]};
		glDiscardFramebufferEXT([B]GL_READ_FRAMEBUFFER_APPLE[/B], [B]3[/B], discard);	
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, surface.framebuffer);
		
	} else {
		// Discard depth only.
		GLenum discard[] = {GL_DEPTH_ATTACHMENT_OES, [B]GL_STENCIL_ATTACHMENT_OES[/B]};
		glDiscardFramebufferEXT([B]GL_FRAMEBUFFER_OES[/B], [B]2[/B], discard);	
	}
#endif

Where in that code do we adjust sample size and whether the whole thing is enabled or not?

I assume it’s safe to replace my app’s AppController.mm file with the one above, or I have to pick out just the MSAA portions and integrate? Will I have to replace the file every time I build?