sRGB doesn't work on FBO with attached depth

Hello, while writing a deferred renderer I ran into a weird issue with sRGB FBOs. If I enable framebuffer sRGB with glEnable(GL_FRAMEBUFFER_SRGB); and then try drawing onto an FBO with a sRGB color attachment, the colors aren’t converted to sRGB, they are written as-is, ONLY if there is also a depth buffer attached to the FBO. If I don’t attach the depth texture it works as expected. I’m on an nvidia 9600gt. I tested on a friend’s AMD card and it works fine either way there. Am I doing something wrong here, am I misinterpreting the spec (which doesn’t mention anything about this not working.) or is this a bug?

Here’s a test case project: http://dl.dropbox.com/u/1990844/2011-10/gamma-bug.7z

And the main file:


#include "gl/VertexArrayObject.hpp"
#include "gl/Shader.hpp"
#include "gl/ShaderProgram.hpp"
#include "gl/Framebuffer.h"
#include "gl/Texture.hpp"
#include "image/ImageLoader.hpp"

#include <iostream>
#include <fstream>

#include "gl3w.hpp"

#include <GL/glfw.h>

void APIENTRY debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, GLvoid* userParam)
{
	if ((type != GL_DEBUG_TYPE_PERFORMANCE_ARB && type != GL_DEBUG_TYPE_OTHER_ARB) || severity != GL_DEBUG_SEVERITY_LOW_ARB)
		std::cerr << message << std::endl;
	if ((type != GL_DEBUG_TYPE_PERFORMANCE_ARB && type != GL_DEBUG_TYPE_OTHER_ARB) || severity == GL_DEBUG_SEVERITY_HIGH_ARB)
		__asm int 3; // Breakpoint
}

bool init_window()
{
	if (glfwInit() != GL_TRUE)
	{
		char tmp;
		std::cerr << "Failed to initialize GLFW." << std::endl;
		std::cin >> tmp;
		return false;
	}

	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3);
	glfwOpenWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	glfwOpenWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
	glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	if (glfwOpenWindow(800, 600, 8, 8, 8, 8, 24, 8, GLFW_WINDOW) != GL_TRUE)
	{
		char tmp;
		std::cerr << "Failed to open window." << std::endl;
		std::cin >> tmp;
		return false;
	}

	glfwSwapInterval(1);

	if (gl3wInit() != 0) {
		char tmp;
		std::cerr << "Failed to initialize gl3w." << std::endl;
		std::cin >> tmp;
		return false;
	} else if (!gl3wIsSupported(3, 3)) {
		char tmp;
		std::cerr << "OpenGL 3.3 not supported." << std::endl;
		std::cin >> tmp;
		return false;
	}

	if (glDebugMessageCallbackARB) {
		glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
		glDebugMessageControlARB(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, 0, GL_TRUE);
		glDebugMessageCallbackARB(debug_callback, 0);
	}

	return true;
}

int main(int argc, char *argv[])
{
	if (!init_window())
		return 1;

	{
		gl::Shader vertex_shader(GL_VERTEX_SHADER);
		gl::Shader frag_shader(GL_FRAGMENT_SHADER);
		{
			std::ifstream f("hack.vert");
			vertex_shader.setSource(f);
		}
		{
			std::ifstream f("hack.frag");
			frag_shader.setSource(f);
		}
		vertex_shader.compile();
		frag_shader.compile();

		gl::ShaderProgram shader_program;
		shader_program.attachShader(vertex_shader);
		shader_program.attachShader(frag_shader);
		shader_program.link();
		if (!shader_program.linkSuccess())
			shader_program.printInfoLog(std::cerr);

		shader_program.use();

		gl::VertexArrayObject vao;
		vao.bind();
		GLuint vbo;
		glGenBuffers(1, &vbo);
		glBindBuffer(GL_ARRAY_BUFFER, vbo);
		static const unsigned int foodata[] = {0, 1, 2};
		glBufferData(GL_ARRAY_BUFFER, sizeof(foodata), &foodata, GL_STATIC_DRAW);
		glVertexAttribPointer(0, 1, GL_UNSIGNED_INT, GL_FALSE, 0, 0);
		glEnableVertexAttribArray(0);

		gl::Texture source_tex;
		{
			image::Image img;
			std::ifstream f("texture.png", std::ios::in | std::ios::binary);
			if (!f)
				return -1;
			image::Image::loadPNGFileRGBA8(img, f);

			source_tex.bind(GL_TEXTURE_2D);

			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

			source_tex.width = img.getWidth();
			source_tex.height = img.getHeight();

			glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, source_tex.width, source_tex.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.getData());
		}

		glUniform1i(shader_program.getUniformLocation("tex"), 0);

		gl::Texture depth_tex;
		depth_tex.bind(GL_TEXTURE_2D);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, 0);

		gl::Texture color_tex;
		color_tex.bind(GL_TEXTURE_2D);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, 800, 600, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);

		gl::Framebuffer fbo;
		fbo.bind(GL_DRAW_FRAMEBUFFER);
		// COMMENT THIS NEXT LINE!!!
		glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_tex, 0);
		glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_tex, 0);
		glDrawBuffer(GL_COLOR_ATTACHMENT0);

		glClearColor(0.f, 0.f, 0.f, 1.f);
		glEnable(GL_FRAMEBUFFER_SRGB);

		bool running = true;
		while (running) {
			fbo.bind(GL_DRAW_FRAMEBUFFER);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			source_tex.bind(GL_TEXTURE_2D);
			glDrawArrays(GL_TRIANGLES, 0, 3);

			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			color_tex.bind(GL_TEXTURE_2D);
			glDrawArrays(GL_TRIANGLES, 0, 3);

			glfwSwapBuffers();

			running = glfwGetWindowParam(GLFW_OPENED) != 0 && glfwGetKey(GLFW_KEY_ESC) != GLFW_PRESS;
		}
	}

	glfwTerminate();

	return 0;
}

After a bit more of investigation, this looks like a driver bug. If I comment out the first glClear call, it now works properly (even if I only clear the color, and not the depth buffer). Furthermore, if after clearing I disable and re-enable GL_FRAMEBUFFER_SRGB, it also makes it work.

How can I report this to nVidia? For now I’m heading over to their developer forums.


// Workaround
fbo.bind(GL_DRAW_FRAMEBUFFER);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_FRAMEBUFFER_SRGB);
glEnable(GL_FRAMEBUFFER_SRGB);
source_tex.bind(GL_TEXTURE_2D);
glDrawArrays(GL_TRIANGLES, 0, 3);

Hi,

Can you also attach the right and wrong images?

Thanks.