Texture updates: something I'm missing or driver quirk?

Started by
4 comments, last by irreversible 9 years, 1 month ago

My OpenGL wrapper is pretty extensive so it's possible I'm missing something very trivial elsewhere. In fact, I'm very strongly inclined towards this, although I've gone over my own code with a pretty fine tooth comb. However, this behavior confounded me for a full day before I finally found the (apparent) root cause and I'm still not convinced why it would occur.

The problem has to do with texture updates via glMapBuffer(). In my case I needed to update two textures side by side - one continuously, one on demand. The former updated properly, the second one behaved in a most peculiar way.

As I tried to update the second texture and use it in two different places (eg rendering two quads with it in slightly different places in my code) I found that one of the calls drew the updated texture while the other one, despite the fact that everything was identical, drew the original texture, no matter what I did.

For the longest time this made absolutely no sense so I decided to dig deep and dismantle my entire texturing pipeline to make sure I wasn't accidentally changing it back, creating a duplicate or whatnot. Everything checked out. Until I compared how I was initializing either of the textures: I was generating the continuous one without any default data; the other one I provided with a default buffer.

Frankly I'm out of ideas as to why one should work as expected and the other should behave so seemingly irrationally, other than suspecting that the driver was quietly restoring the original texture data without me implicitly telling it to do so. Except that it makes no sense for the texture data to be persistent. The PBO update is asynchronous and identical in both cases. Additionally, since I'm still not finished on the driver wrapper, I have a tendency to check each and every GL call for errors and they all pass with flying colors.

Unless I'm blind, this is the only thing the docs have to say about the data pointer for glTexImage2D():

data may be a null pointer. In this case, texture memory is allocated to accommodate a texture of width width and height height. You can then download subtextures to initialize this texture memory. The image is undefined if the user tries to apply an uninitialized portion of the texture image to a primitive.

This specifies nothing about the behavior of a texture with default data.

Is this normal? Was I missing something incredibly trivial all these years and somehow just managed to get lucky with my textures? Or am I just not reading between the lines?

Incidentally, just to be sure I also updated my Nvidia drivers and the behavior is identical on current drivers and drivers from 7 months ago.

Advertisement

Firstly, you want to be careful with OpenGL docs. You've linked to the OpenGL ES version of glTexImage2D which won't necessarily be the same as the one in OpenGL. You'l notice there's no mention of pixel buffer objects on that page.

It does mention the data parameter in a few places, but it's inline with the function description:

Data is read from data as a sequence of unsigned bytes or shorts, depending on type [...]

In short, if you don't have a pixel buffer object bound when you're calling glTexImage2D, then the pixel data in the pointer provided is stored in the texture. If one is bound, then the pixel data will be copied from the pixel buffer object, using data as an offset into the buffer.

As I tried to update the second texture and use it in two different places (eg rendering two quads with it in slightly different places in my code) I found that one of the calls drew the updated texture while the other one, despite the fact that everything was identical, drew the original texture, no matter what I did.

That sounds like an issue with the driver to me. I'm fairly certain that draw calls following a glTexImage2D call should wait on pending texture uploads if it happens asynchronously (in the case of using a pixel buffer object). Do you have any other graphics cards from a different vendor that you can test this on?


Firstly, you want to be careful with OpenGL docs. You've linked to the OpenGL ES version of glTexImage2D which won't necessarily be the same as the one in OpenGL. You'l notice there's no mention of pixel buffer objects on that page.

Ah - indeed, I googled glTexImage2D directly and picked the first link. Thanks for pointing it out.


In short, if you don't have a pixel buffer object bound when you're calling glTexImage2D, then the pixel data in the pointer provided is stored in the texture. If one is bound, then the pixel data will be copied from the pixel buffer object, using data as an offset into the buffer.

This reads like normal expected behavior and is what I assumed in the first place.


[quote name='Irreversible']
As I tried to update the second texture and use it in two different places (eg rendering two quads with it in slightly different places in my code) I found that one of the calls drew the updated texture while the other one, despite the fact that everything was identical, drew the original texture, no matter what I did.[/quote]
That sounds like an issue with the driver to me. I'm fairly certain that draw calls following a glTexImage2D call should wait on pending texture uploads if it happens asynchronously (in the case of using a pixel buffer object). Do you have any other graphics cards from a different vendor that you can test this on?

I'm on a 750M right now. I do have a 460GTX on my desktop, but I don't think even any of my friends have a non-Nvidia card.

Overall this seems like a very obvious bug that should rear its head pretty quickly. Especially since I encountered this on v342.52, which was released last June or July and it's still here in the latest driver build (v347.52 from a few days ago).

I'd suggest that you test this without the PBO.

A PBO is really only useful if you're not using the texture until a few frames after you update it, otherwise you'll likely find that just using glTexSubImage2D (not glTexImage2D which will fully respecify the texture) gives better performance and more predictable behaviour.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

I don't quite understand what you are doing with "default buffers" or what these are (maybe a few lines of code would help better understanding what you're doing).

Regardless, glTexImage2D has three (not two) modes of operation:

  1. No buffer object is bound and data = nullptr ? Reserve memory for a texture of the specified size, do nothing else (not even zero memory). If something happens to be in that memory, you will see it. Whatever it is, it's undefined. It might just as well be some old buffer's contents that "looks about right" but is a few frames old.
  2. No buffer object is bound and data != nullptr ? Reserve memory for a texture of the specified size, and then read pixel data from data. Presumably this works by copying into an unnamed buffer object nowadays, but it's still OpenGL 1.1 functionality. Which, in any case, must run synchronously since the driver cannot know for how long the data pointed to by data remains valid, so don't expect stellar performance.
  3. Buffer object is bound ? Reserve memory for a texture of the specified size, type-pun data into an unsigned integer, and read pixel data from the buffer object starting at the offset data. This is what you will want to do in almost every case.

glTexSubImage2D, by contrast, only knows two very similar modes of operation: Copy data either from a buffer object (if any) or from the pointer data (if there's no buffer) into already allocated texture storage.

do nothing else (not even zero memory). If something happens to be in that memory, you will see it. Whatever it is, it's undefined. It might just as well be some old buffer's contents that "looks about right" but is a few frames old.

Keep in mind that this specific part of the behaviour is not explicitly guaranteed. Some systems explicitly zero the memory as a security precaution (i.e. to prevent you reading back the content from another app's framebuffer, and OCR'ing any text).

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

I'd suggest that you test this without the PBO.

A PBO is really only useful if you're not using the texture until a few frames after you update it, otherwise you'll likely find that just using glTexSubImage2D (not glTexImage2D which will fully respecify the texture) gives better performance and more predictable behaviour.

I'm pretty sure I did try a direct upload as well. I'll check again when I get some time.


I don't quite understand what you are doing with "default buffers" or what these are (maybe a few lines of code would help better understanding what you're doing).

The default contents are just the initial texture data. I'm creating my textures synchronously, because I'm already marshalling the commands to the render thread. In practice this means that my code fails to modify a texture that is created as-is when it is allocated, but can modify a texture buffer that is reserved, but whose data is not copied at creation time. Makes sense?

This topic is closed to new replies.

Advertisement