Error when using blitFramebuffer

Started by
9 comments, last by MausGames 9 years, 6 months ago

Hi, I'm copying textures using glBlitFramebuffer like this:


glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src->handle(), 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst->handle(), 0);
glBlitFramebuffer(0, 0, src->size().x, src->size().y, 0, 0, dst->size().x, dst->size().y, GL_COLOR_BUFFER_BIT, mode);

The framebuffer is freshly created and has no other attachments. src is 512x512 and dst is 256x256 and mode is set to linear filtering.

I got an error callback that says

GL_INVALID_FRAMEBUFFER_OPERATION error generated. Framebuffer bindings are not framebuffer complete.

So after setting the attachments I checked both READ and WRITE fb with glCheckFramebufferStatus. The WRITE fb returns GL_FRAMEBUFFER_COMPLETE, but to my surprise the READ fb returns GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT (I'm not even using this old extension).

I managed to workaround the issue by first setting src attachment to both READ and DRAW fb and then replacing the draw:


glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src->handle(), 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst->handle(), 0);

but this seems unnecessary to me. The curious thing is that if I first set both buffers to dst and then change only READ I still get the error in the READ fb.

I'm using OpenGL 4.5 context (beta NVidia driver on GTX780).

I thought I should be able to set READ and WRITE buffers to different dimensions since OpenGL 3.something without such issues?

Could this be a driver bug or am I missing something about the framebuffer setup?

Advertisement

Im a little confused with the use of glFramebufferTexture2D to do a blit, unless those two lines are from the fbo setup. glFramebufferTexture2D attaches a texture to a framebuffer attachment point allowing you to render into the texture.

To blit:

glBindFramebuffer(GL_READ_FRAMEBUFFER, srcID) // read from this buffer

glReadBuffer(GL_COLOR_ATTACHMENT0) // specifically this attachment

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstID) // draw/write to this buffer

glDrawBuffer(GL_COLOR_ATTACHMENT0) // specifically this attachment

glBlitFramebuffer(...) // do the blit

glBindFramebuffer(GL_READ_FRAMEBUFFER, 0) // clear read

glReadBuffer(...) // reset read

by calling glFramebufferTexture2D, youre doing attachments and possibly detaching, which is sometimes needed depending on what youre doing, Im just not sure if you need it here.

glFramebufferTexture2D attaches a texture to a framebuffer attachment point allowing you to render into the texture.

Yes, OR to read from when it's a GL_READ_FRAMEBUFFER.

I omitted the binding code but I have a single FBO with one texture attached to the READ buffer attachment point 0 and another to the WRITE buffer attachment point 0.

This is a nice setup for fast copying and it works (worked actually :/ ) great, except for that weirdness I mentioned.

The whole code with a binding looks something like this:


void Effects::copyTexture(GameTexture* src, GameTexture* dst, GLenum mode)
{
  glBindFramebuffer(GL_FRAMEBUFFER, copyFbo->handle());
  glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src->handle(), 0);
  glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst->handle(), 0);
  glBlitFramebuffer(0, 0, src->size().x, src->size().y, 0, 0, dst->size().x, dst->size().y, GL_COLOR_BUFFER_BIT, mode);
  glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

I vaguely remember that it worked like that without errors some time ago (an older driver). That's why I asked if this could be a driver bug.

Hm, upon further inspection of the spec I see that GL_FRAMEBUFFER is equivalent to GL_DRAW_FRAMEBUFFER (I thought it's both read and write), so I'm wondering how the whole thing worked at all... but it did! Oh well, I need to rewrite this correctly I guess...

Ok, turns out because I thought that binding to the GL_FRAMEBUFFER binds to both READ and WRITE buffers I was actually trying to attach the texture to a null READ buffer.

The fixed code looks like this:


void Effects::copyTexture(GameTexture* src, GameTexture* dst, GLenum mode)
{
  glBindFramebuffer(GL_READ_FRAMEBUFFER, copyFbo->handle());
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, copyFbo->handle());
  glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src->handle(), 0);
  glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst->handle(), 0);
  glBlitFramebuffer(0, 0, src->size().x, src->size().y, 0, 0, dst->size().x, dst->size().y, GL_COLOR_BUFFER_BIT, mode);
  glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}

and now it works as expected without errors. Gosh, I'm making stupid mistakes. I need a coffee...

Sorry for bothering you.

How does this even work?

You have one single FBO, you bind it to both targets, then you attach the source texture to GL_COLOR_ATTACHMENT0 of that FBO and then you overwrite it with the destination texture (same FBO) - so you are basically blitting from the destination texture to the destination texture which has undefined behavior.

There is no separate READ and DRAW attachment inside the (single) FBO container.

Sorry for the late reply.

Sure there is. If they weren't separate why even bother to specify different targets for them in glFramebufferTexture2D call?

The way I see it READ and WRITE locations are totally separately configurable. It just so happens that for most cases you don't even care about the READ attachments, because there's no programmable way to read them in the shaders (this would give us for example programmable blending stage which we sadly don't have yet :( ). It's only for the "fixed" functions like the glBlitFramebuffer or such.

Anyway, I didn't bother to look it up in the spec but It's perfectly reasonable for me to assume that if I can specify different targets(READ or WRITE) then it's used to actually make a distinction. As far as I can tell it indeed works on hardware I could get access to, so either there's a global driver makers misunderstanding (aligned with what I think) or... well I don't know what's the other option.

Ok, I'm starting to be really hang up on the issue. I've re-read your comments, read the spec and did some more search on the topic and I can't seem to arrive at a common conclusion.

On one hand the READ and WRITE targets can be viewed like I described them - as a sort of 2 different objects inside an FBO having different attachments. This seems reasonable in the scenario of a single FBO and seems to be backed up by the working example.

The other view is that there's a single attachment point 0 (or any other) on an FBO targeted for both reading and writing. In that case READ and WRITE targets seem to make sense in case of two different FBOs, one of which is targeted for reading and another for writing. This interpretation is backed up by the fact that the new bindless syntax doesn't allow to specify different attachment 0 for reading and writing. The glNamedFramebufferTexture* functions take only an FBO id.

I'm really confused now. Spec is unclear on this (or I can't find relevant part). Is it a spec defect? A driver bug? An undefined behavior? Or perfectly valid (though uncommon) code?

The first argument of the glFramebufferTexture functions is the target framebuffer you want to be manipulating.

There are two framebuffer bindings - the draw framebuffer, which is used for drawing to, and the read framebuffer, which is where you read from. When you call glBindFramebuffer(GL_FRAMEBUFFER, fbo); you bind 'fbo' to both the draw framebuffer and the read framebuffer. Alternatively, you can bind specifically to the draw or read framebuffer by specifying GL_DRAW_FRAMEBUFFER or GL_READ_FRAMEBUFFER respectively.

Coming back to glFramebufferTexture, when you do glFramebufferTexture2D(GL_READ_FRAMEBUFFER, ...); you modify the framebuffer which is currently bound as the 'read framebuffer'. With GL_DRAW_FRAMEBUFFER, you modify the framebuffer which is currently bound as the 'draw framebuffer'. If the draw and read framebuffers are the same (if you used GL_FRAMEBUFFER when binding) then in both cases you're modifying the same framebuffer.

Ok, turns out because I thought that binding to the GL_FRAMEBUFFER binds to both READ and WRITE buffers I was actually trying to attach the texture to a null READ buffer.

The fixed code looks like this:


void Effects::copyTexture(GameTexture* src, GameTexture* dst, GLenum mode)
{
  glBindFramebuffer(GL_READ_FRAMEBUFFER, copyFbo->handle());
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, copyFbo->handle());
  glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src->handle(), 0);
  glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst->handle(), 0);
  glBlitFramebuffer(0, 0, src->size().x, src->size().y, 0, 0, dst->size().x, dst->size().y, GL_COLOR_BUFFER_BIT, mode);
  glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}

and now it works as expected without errors. Gosh, I'm making stupid mistakes. I need a coffee...

Sorry for bothering you.

As far as I know, the driver is free to do whatever it wants in this case because you're reading from and writing to the same framebuffer texture. Some drivers will be clever enough to spot this and allocate new texture memory to draw to (so that the original is kept), and once the blit is finished, destroy the old texture and swap in the new one. However, it could be that the driver doesn't do anything and screws you over by corrupting the texture :P

If all you are using the FBO for is to copy the texture then you can use glCopyTexImage2D instead.

Thanks for the patience. I think I understand now. So this code is of "hey, works on my machine" variety. I'll fix that.

Btw. it seems inconsistent that for glBindFramebuffer GL_FRAMEBUFFER means both read and write targets, but for glFramebufferTexture it means only the draw target. Or am I missing something again?

glCopyTexImage2D only lets me copy a rectangle portion 1:1. I need to resize the texture (with filtering) in the process sometimes, so that's why I'm using FBO. Is there a faster way?


Btw. it seems inconsistant that for glBindFramebuffer GL_FRAMEBUFFER means both read and write targets, but for glFramebufferTexture it means only the draw target. Or am I missing something again?

It's just an inconsistency unfortunately :(


glCopyTexImage2D only lets me copy a rectangle portion 1:1. I need to resize the texture (with filtering) in the process sometimes, so that's why I'm using FBO. Is there a faster way?

I'm not sure on this but maybe you could attach one texture to GL_COLOR_ATTACHMENT0 and another to GL_COLOR_ATTACHMENT1. And then set the read attachment using glReadBuffer(GL_COLOR_ATTACHMENT0); the draw buffer using glDrawBuffer(GL_COLOR_ATTACHMENT1); and then perform the blit. Failing that, you would need to create two FBOs, one for drawing to, one for reading from.

This topic is closed to new replies.

Advertisement