FBO Corruption on ATi/Mac

I have an game which uses FBOs to do 2d rendering to compose multiple layers. This works fine on the windows machines I’ve tested it on (all nVidia cards) but on a friends laptop it produces corrupt output. It’s a Mac with an ATi Radeon 9600 XT.

I’ve distilled this down to a minimum reproduction case, which does something like:

  • Create single FBO
  • Create two empty textures to use with FBO
  • Render:
    – Bind FBO
    – Attach texture 1 to FBO
    – Draw into texture 1
    – Attach texture 2 into FBO
    – Draw into texture 2
    – Detach FBO
    – Draw texture 1 to screen
    – Draw texture 2 to screen

Code:


	public FboTest5() throws Exception
	{
		final boolean fboCapable = GLContext.getCapabilities().GL_EXT_framebuffer_object;
		if (!fboCapable)
			throw new RuntimeException("FBOs not supported
");
		
		sprite = new Sprite("Textures/Tails.png");
		
		x = 200;
		y = 200;
		
		// Gen FBO
		IntBuffer buffer = BufferUtils.createIntBuffer(1);
		EXTFramebufferObject.glGenFramebuffersEXT(buffer); 
		fboId = buffer.get();
		if (fboId == 0)
			throw new RuntimeException("Couldn't create FBO");
		
		// Gen textures
		shadowTex = createBlankTexture(256, 256);
		fullbrightTex = createBlankTexture(256, 256);
		
		Util.checkGLError();
	}


public void render()
	{	
		Util.checkGLError();
		
		setDefaultState();
		
		// Shadow RTT
		
		// Bind fbo
		EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, fboId);
		
		// Attach our target texture
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
		EXTFramebufferObject.glFramebufferTexture2DEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT,
               											GL11.GL_TEXTURE_2D, shadowTex, 0);
		checkCompleteness(fboId);
		{
			GL11.glViewport(0, 0, 256, 256);
			setOrtho(Testbed.SCREEN_WIDTH, Testbed.SCREEN_HEIGHT);
		
			GL11.glClearColor(1f, 1f, 1f, 0f);
			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
			
			GL11.glColor4f(0f, 0f, 0f, 1f);
				
			sprite.draw(x-20, y+40);
			sprite.draw(x+20, y+40);
		}
		
		// Fullbright RTT
		
		// Attach our target texture
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
		EXTFramebufferObject.glFramebufferTexture2DEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT,
               											GL11.GL_TEXTURE_2D, fullbrightTex, 0);
		checkCompleteness(fboId);
		{
			GL11.glViewport(0, 0, Testbed.SCREEN_WIDTH, Testbed.SCREEN_HEIGHT);
			setOrtho(Testbed.SCREEN_WIDTH, Testbed.SCREEN_HEIGHT);
			
			GL11.glClearColor(0.4f, 0.4f, 0.8f, 0f);
			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
			
			
			// Normal, full colour sprite
			GL11.glColor4f(1f, 1f, 1f, 1f);
			sprite.draw(x, y);
			sprite.draw(x-20, y);
			sprite.draw(x+20, y);
		}
		// Release fbo
		EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0);
		
		
		// Final framebuffer render
		
		GL11.glViewport(0, 0, Testbed.SCREEN_WIDTH, Testbed.SCREEN_HEIGHT);
		
		GL11.glClearColor(0.4f, 0.4f, 0.8f, 0f);
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
		{
			setOrtho(Testbed.SCREEN_WIDTH, Testbed.SCREEN_HEIGHT);

			// Fullbright RTT
			// draw RTT result as whole screen quad
			{
				GL11.glBindTexture(GL11.GL_TEXTURE_2D, fullbrightTex);
				
				float width = Testbed.SCREEN_WIDTH;
				float height = Testbed.SCREEN_HEIGHT;
				
				GL11.glBegin(GL11.GL_QUADS);
				{
					GL11.glColor4f(1, 1, 1, 0.5f);
					GL11.glTexCoord2f(0f, 0f);
					GL11.glVertex2f(0, 0);
					
					GL11.glTexCoord2f(1, 0f);
					GL11.glVertex2f(width, 0);
					
					GL11.glTexCoord2f(1, 1);
					GL11.glVertex2f(width, height);
					
					GL11.glTexCoord2f(0f, 1);
					GL11.glVertex2f(0, height);
				}
				GL11.glEnd();
			}
			
			// Shadow RTT
			{
				GL11.glBindTexture(GL11.GL_TEXTURE_2D, shadowTex);
				
				float width = Testbed.SCREEN_WIDTH;
				float height = Testbed.SCREEN_HEIGHT;
				
				GL11.glBegin(GL11.GL_QUADS);
				{
					GL11.glColor4f(1, 1, 1, 0.5f);
					GL11.glTexCoord2f(0f, 0f);
					GL11.glVertex2f(0, 0);
					
					GL11.glTexCoord2f(1, 0f);
					GL11.glVertex2f(width, 0);
					
					GL11.glTexCoord2f(1, 1);
					GL11.glVertex2f(width, height);
					
					GL11.glTexCoord2f(0f, 1);
					GL11.glVertex2f(0, height);
				}
				GL11.glEnd();
			}
		}
	}

The full code is here: http://www.triangularpixels.com/Junk/FboTest5.java

I’m a bit of a n00b with FBOs but as far as I can tell this is correct usage of an fbo. My only hint so far is that it seems to be related to having two render-to-texture passes - if I have only a single pass (and a single target texture) then it works as expected.

Any hints would be greatly appreciated, thanks.

Some further tinkering shows that if I unbind and then rebind the FBO before changing the attached texture then it works on ATi. To me that doesn’t make much sense - does anyone know if that is correct behaviour or an ATi quirk?

Looks like a driver bug… Are the latest ATI drivers installed on the testing machine?

If it’s on a Mac then don’t hold your breath for the drivers to be “latest”. :wink:

Although right now there is a good chance that either with the next point release of the current OS, or with the release of Snow Leopard that OpenGL drivers will have a minor / major make over on OS X.

So, it is worth filing a bug report with Apple for this, especially right now.