Framebuffer has incomplete attachment, when using multisampled textures

I am trying to render to texture for post-processing. Preparing the Framebuffer with simple textures for the color and depth attachment works fine as shown in this minimal example which produces a blue triangle on a black background:

glEnable(GL_MULTISAMPLE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);

GLuint fbo = 0;
glGenFramebuffers(1, &fbo);
GLuint colorTexture, depthTexture;
glGenTextures(1, &colorTexture);
glGenTextures(1, &depthTexture);

GLint dims[4] = {0};
glGetIntegerv(GL_VIEWPORT, dims);
GLint fbWidth = dims[2];
GLint fbHeight = dims[3];

// SIMPLE TEXTURE
glBindTexture(GL_TEXTURE_2D, colorTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fbWidth, fbHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glBindTexture(GL_TEXTURE_2D, depthTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fbWidth, fbHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);

if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) {
        std::cout << "incomplete attachment" << std::endl;
    }
}

// draw triangle to texture
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(0.0f,0.0f,1.0f);
glBegin(GL_TRIANGLES);
glVertex3f(-1.0f, -1.0f, 0.0f);
glVertex3f(1.0f, -1.0f, 0.0f);
glVertex3f(0.0f, 1.0f, 0.0f);
glEnd();


// draw texture to screen
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, colorTexture);
glBegin(GL_QUADS);
glTexCoord2f(1.0, 0.0); glVertex3f(1.0f, -1.0f, 0.0f);
glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f, -1.0f, 0.0f);
glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f, 1.0f, 0.0f);
glTexCoord2f(1.0, 1.0); glVertex3f(1.0f, 1.0f, 0.0f);
glEnd();
glDisable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);

But when I change the texture-related calls to the multisampling equivalents, I get an incomplete Framebuffer error (36054, incomplete attachment)
Here’s what the multisampling code looks like:

glEnable(GL_MULTISAMPLE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);

GLuint fbo = 0;
glGenFramebuffers(1, &fbo);

GLuint colorTexture, depthTexture;
glGenTextures(1, &colorTexture);
glGenTextures(1, &depthTexture);

GLint dims[4] = {0};
glGetIntegerv(GL_VIEWPORT, dims);
GLint fbWidth = dims[2];
GLint fbHeight = dims[3];

// MULTISAMPLED TEXTURE
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, colorTexture);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA, fbWidth, fbHeight, 0);
glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, depthTexture);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_DEPTH_COMPONENT, fbWidth, fbHeight, 0);
glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, colorTexture, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE, depthTexture, 0);

if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) {
        std::cout << "incomplete attachment" << std::endl;
    }
}

// draw triangle to texture
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(0.0f,0.0f,1.0f);
glBegin(GL_TRIANGLES);
glVertex3f(-1.0f, -1.0f, 0.0f);
glVertex3f(1.0f, -1.0f, 0.0f);
glVertex3f(0.0f, 1.0f, 0.0f);
glEnd();


// draw texture to screen
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, colorTexture);
glBegin(GL_QUADS);
glTexCoord2f(1.0, 0.0); glVertex3f(1.0f, -1.0f, 0.0f);
glTexCoord2f(0.0, 0.0); glVertex3f(-1.0f, -1.0f, 0.0f);
glTexCoord2f(0.0, 1.0); glVertex3f(-1.0f, 1.0f, 0.0f);
glTexCoord2f(1.0, 1.0); glVertex3f(1.0f, 1.0f, 0.0f);
glEnd();
glDisable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);

I know that I have to blit the multisampled textures into simple textures before using them, but since the Framebuffer is not complete, I’m not there yet.
What am I doing wrong?

I ran your code in my own engine and got this error [Core-Error]

GL_INVALID_ENUM error generated. multisample textures targets doesn’t support sampler state

and if we have a look at where this breakpoint was it was on the code

glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

remove these parameters and the code works fine. Remember you are using a multisampled texture, pixels that are sampled multiple times linearly and average the results. once you blit the framebuffer over to a normal texture you can have those paramters on the other texture when filtering.

Just another tip to catch these types of errors. I would recommend enabling something called opengl debug callbacks. Which is essentially a validation layer which catches any opengl call error as soon as you run a function. I have setup mine like this for example.

static void OpenGLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
{
	switch (severity)
	{
	case GL_DEBUG_SEVERITY_LOW:
		TGE_LOG_INFO(message);
		TGE_CLIENT_LOG(message);
		break;
	case GL_DEBUG_SEVERITY_MEDIUM:
		TGE_LOG_WARN(message);
		TGE_CLIENT_WARN(message);
		//TGE_HALT();
		break;
	case GL_DEBUG_SEVERITY_HIGH:
		TGE_LOG_ERROR(message);
		TGE_CLIENT_ERROR(message);
		TGE_HALT(); //in my case is just defined as __debugbreak() but can be any logic you like
		break;
	default:
		break;
	}
}

I halt when there is an error so I can use the call stack to see what functions were called with that error. Then after loading opengl you can call

glDebugMessageCallback(OpenGLDebugCallback, nullptr);

1 Like