PDA

View Full Version : Scops of Textures and other GL objects



Godspeed
06-02-2016, 03:49 PM
Hi,

I am not very clear about the scope of texture units in OpenGL 3.3+. As an example, lets say we have 2 c++ classes: Apple and Orange. I created and bind a texture and texture sampler in Apple using glBindTexture() and glBindSampler().

Now in case 1: Orange wants to use exactly the same texture, can I just access it by setting the Orange's sampler uniform in shader to whatever number I set in glBindSampler() in Apple?

In case 2: Orange is using a brand new texture, I used glBindTexture() and glBindSampler() again with new data, but glBindSampler() binds the new texture to the same sampler as the one used in Apple, will this cause problem? In another word, I used texture sampler 0, 1, 2 in Apple for rendering it's texture, Can I reuse the same sampler in Orange? What about texture name?

I also have the same question for Uniform Buffers and other buffer objects, once I bind it in Apple with it's shader, can I bind it again in Orange with it's shader?

Thanks!

GClements
06-02-2016, 04:29 PM
I am not very clear about the scope of texture units in OpenGL 3.3+. As an example, lets say we have 2 c++ classes: Apple and Orange.

Let's say we forget about C++ classes and just talk about OpenGL, which neither knows nor cares anything about classes.



I created and bind a texture and texture sampler in Apple using glBindTexture() and glBindSampler().

So you create and bind a texture. Where you bind them doesn't matter. From OpenGL's perspective, a texture and a sampler are bound to a particular texture unit.



Now in case 1: Orange wants to use exactly the same texture, can I just access it by setting the Orange's sampler uniform in shader to whatever number I set in glBindSampler() in Apple?

Are you getting confused between sampler objects (glGenSamplers(), glBindSampler() etc) and GLSL "sampler" types (sampler2D etc)?

That would be understandable given the naming, but they're not directly related. A sampler object is a container (similar to a C struct) for texture-related state which historically has been part of the texture. Actually, it still is part of the texture, but sampler objects allow you to store the parts of the state related to sampling (e.g. filter and repeat modes) separately from the texture.

Textures are accessed by binding them to a texture unit then storing the index of the texture unit in a uniform variable of sampler type. Additionally, you can bind a sampler object to a texture unit; if you do that, the parameters stored in the sampler override those stored in the texture.

Texture sampling state can be manipulated either using glTexParameter() (which affects the sampler bound to the active texture unit, or the texture bound to the active texture unit if no sampler is bound to it) or using glTextureParameter() (which affects a specific texture referenced by name), or using glSamplerParameter() (which affects a specific sampler object referenced by name).

Unless you have an actual need to use sampler objects, it would be simpler to forget about them initially and just store the sampling state in the texture. I.e. don't call glGenSamplers() or glBindSampler(), and just use glTexParameter() or glTextureParameter() to store the sampling state in the texture.

Samplers are useful mainly if you regularly need to change the sampling parameters for a texture, or for multiple textures, as you can change all of the sampling state for a texture unit with a single glBindSampler() call, or for multiple textures by binding a single sampler object to multiple texture units and changing the sampler object's state (which will then affect all of the texture units to which it's bound).

Godspeed
06-02-2016, 04:47 PM
Thanks for the reply, I think my question is more related to the life time of those GL objects rather than the confusion of the samplers. In C/C++, every variable has a lift time, when the execution goes out of the local scope, the local variables are deleted. I am just wandering if this is the same for any GL objects.

In a real example: I have 2 C++ objects, one renders the sky, the other renders the water. The object that renders the water would like to use some texture from the sky class for rendering the reflections. Do I need to load those sky textures from the hard drive again or should the shader in the water class use use the one used by sky class?

In traditional C/C++ sense, I would expect that I have to load it again, since the texture in sky class is now out of scope when rendering in the water class. However it doesn't seem that way, OpenGL's scope of variables/objects are kind of different. For example, for a Uniform block can be bind in one shader program and I can leave it that way while I change my shader program! And the new shader program is going to see the current set of uniform values. This defies my understanding of the traditional C/C++ sense of scope.

So are other GL objects like the aforementioned textures behave the same? (Change from rendering sky to rendering water is essentially a change of shader program)

Alfonse Reinheart
06-02-2016, 05:26 PM
I am just wandering if this is the same for any GL objects.

... How could it be? OpenGL object names are integers; how could OpenGL possibly know that a `GLuint` variable holding an object fell off the bottom of the stack?

Not only that, they have explicit creation and destruction functions. Clearly, their lifetimes are not inherently associated with any sort of "scope".

OpenGL object lifetimes are like the lifetimes of dynamic memory allocations in C++. Every `new` needs a `delete`. You can even wrap OpenGL objects in smart pointer-like constructs.


I have 2 C++ objects, one renders the sky, the other renders the water. The object that renders the water would like to use some texture from the sky class for rendering the reflections. Do I need to load those sky textures from the hard drive again or should the shader in the water class use use the one used by sky class?

This is the problem with over-object-oriented thinking. You limit yourself and how you think of problems.

There is no such thing as a "sky texture". There is merely a texture, which contains some data. The objects that have to use it are the objects who should have some control over the lifetime of it. If you want your "sky" object and "water" object to use the same texture, then they need to access the same texture.

Indeed, you probably shouldn't have a "sky object" or a "water object". It sounds more like you have two renderable constructs, both of which use one or more programs, some buffers for vertex data and uniforms, and some textures. These objects sound almost identical in terms of their storage and behavior; the only thing that's different is what programs, mesh data, and texture data they happen to use.


In traditional C/C++ sense, I would expect that I have to load it again

What you describe is not "traditional C/C++" in any sense. If for no other reason than that C doesn't have classes or automatic lifetime of any kind. It has stack variables, but they don't have constructors and destructors.

Furthermore, rendering happens in a loop. You wouldn't be creating these sky and water objects within that loop. So if you created them on the stack, their scopes would have to extend across the whole of the loop.

Most applications of any real size have to do more than just rely on stack variables. You will need to use dynamic memory management techniques.


For example, for a Uniform block can be bind in one shader program

No, uniform buffers cannot be bound to shader programs at all. You associate a particular uniform block in a shader to a location in the context where a buffer is bound. The buffer object with the data is not directly associated with the shader.

GClements
06-02-2016, 05:34 PM
Thanks for the reply, I think my question is more related to the life time of those GL objects rather than the confusion of the samplers. In C/C++, every variable has a lift time, when the execution goes out of the local scope, the local variables are deleted. I am just wandering if this is the same for any GL objects.

How could it be? OpenGL knows nothing about the structure of your program. It only knows which OpenGL functions are called, in what order, and with which parameters.

Once an object is created, it exists until either the object is explicitly destroyed (and perhaps a bit longer; objects referenced by pending commands which have been issued but not completed will exist internally until the commands have completed) or the context to which the object belongs has been destroyed (most toolkits manage contexts automatically; typically, contexts are associated with windows and persist until the window is destroyed).



In a real example: I have 2 C++ objects, one renders the sky, the other renders the water. The object that renders the water would like to use some texture from the sky class for rendering the reflections. Do I need to load those sky textures from the hard drive again or should the shader in the water class use use the one used by sky class?

A texture is created the first time that a name allocated with glGenTextures() is bound. It persists until deleted with glDeleteTextures() (or the context which owns it is destroyed). Its contents and associated state persist until explicitly changed.



In traditional C/C++ sense, I would expect that I have to load it again, since the texture in sky class is now out of scope when rendering in the water class.

"Scope" is a construct of the language you use to generate the executable. OpenGL doesn't get to know about any of that.

Anything which has a name (textures, samplers, buffers, shaders, programs, etc; anything with a glGen* or glCreate* function and a corresponding glDelete* function) is analogous to dynamically-allocated memory in C (malloc/free) or C++ (new/delete). It exists from the point it's created to the point that it's destroyed. If you create such an object then lose track of its name, you effectively have a resource leak.

Anything else is (roughly) analogous to a global variable. That's somewhat simplified because you can have multiple contexts (these aren't part of the OpenGL API, but part of the OS' window-system APIs), but that typically isn't relevant unless you're creating an application with multiple rendering windows or using multiple threads for rendering.

mhagain
06-03-2016, 12:02 AM
State is global. It might be more helpful to think of operations on state as being analogous to setting a global variable - it doesn't matter where you set it, the global variable will have that value everywhere.

john_connor
06-03-2016, 02:39 AM
i personally wrap the GL objects in classes, just for allocation / releasing and additionally i explicitly delete copy constructor & assignment operator to be sure that i dont mess with the generation of GL objects:
opengl.h


class GLObject
{
public:

GLObject();
virtual ~GLObject();

unsigned int ID() const;

protected:

unsigned int m_id{ 0 };

// NOT allowed to be copied
GLObject(const GLObject& other) = delete;
GLObject& operator=(const GLObject& other) = delete;

};


class Buffer : public GLObject
{
public:

Buffer();
virtual ~Buffer();

};

// etc ...


opengl.cpp


GLObject::GLObject()
{}

GLObject::~GLObject()
{}

unsigned int GLObject::ID() const
{
return m_id;
}

Buffer::Buffer()
{
glGenBuffers(1, &m_id);
}

Buffer::~Buffer()
{
glDeleteBuffers(1, &m_id);
}

// etc ... all other objects like vertex array / shader program here


another thing regarding textures:
i use a singelton "resource" class in which i wrap the texture loading, everytime i want a texture, i first check if i've already loaded that texture, if not, i'll create it and map it to its filepath:


unsigned int Resources::LoadTexture(const std::string & filepath)
{
// protected member: std::map<std::string, unsigned int> m_textures;


if (filepath.empty())
return 0;

if (filepath[filepath.length() - 1] == '\/')
return 0;

std::map<std::string, unsigned int>::iterator it = m_textures.find(filepath);
if (it != m_textures.end())
return it->second;

// textures
unsigned int texture = SOIL_load_OGL_texture
(
filepath.c_str(),
SOIL_LOAD_AUTO,
SOIL_CREATE_NEW_ID,
SOIL_FLAG_MIPMAPS | SOIL_FLAG_NTSC_SAFE_RGB | SOIL_FLAG_COMPRESS_TO_DXT
);

if (!texture)
{
std::cout << "ERROR: Resources::LoadTexture(...) cant create texture: " << std::endl << filepath << std::endl << std::endl;
return 0;
}

//std::cout << "Resources::LoadTexture(...) creating texture: " << std::endl << filepath << std::endl << std::endl;
m_textures.emplace(std::pair<std::string, unsigned int>(filepath, texture));

return texture;
}



everything other than textures are protected members of the specific class in which they are use, for example every model (class) has 1 buffer, 1 vertexarray, and 1 shared (static) buffer for instanciation

GClements
06-03-2016, 06:05 AM
i personally wrap the GL objects in classes, just for allocation / releasing
This is potentially problematic if you use more than one context, as deleters (glDelete* functions) should only be called when a context containing the object is bound. Calling glDelete* when no context is bound will result in some form of error; calling it when a different context is bound may delete the wrong object (object names are only unique within a given context or a group of contexts which share data).

At least in C++ it's possible to deal with this issue, as ultimately you can determine when a destructor is called. In OO languages with delayed finalisation (e.g. Java or Python), a destructor can be called at any time after an object becomes unreachable, and there's no guarantee which context is bound (or whether any context is bound) at that point.



another thing regarding textures:
i use a singelton "resource" class in which i wrap the texture loading, everytime i want a texture, i first check if i've already loaded that texture, if not, i'll create it and map it to its filepath:

This too can be problematic if you're using multiple contexts which don't share data. On systems with multiple video devices, contexts can typically only share data if they correspond to the same device, so applications which can handle multiple screens (which aren't provided by a single video device) typically have to create each texture, buffer etc separately for each device on which it is to be used. I.e. you need a separate resource manager instance per device, so the resource manager shouldn't literally be a singleton (this is a fairly good example of why singletons at least tend to be overused and at worst are a bad idea in general).

Alfonse Reinheart
06-03-2016, 06:17 AM
unsigned int m_id{ 0 };


GLObject(const GLObject& other) = delete;
GLObject& operator=(const GLObject& other) = delete;



So let me get this straight.

You know that C++11 exists. You're using C++11 features. You created an encapsulated RAII class for OpenGL objects.

And you didn't give it move support? You were smart enough to know that deleting the copy operations was important; how did you miss the lesson on move semantics ;)

Also, I have to say that your choice to make this `GLObject` a virtual base class is a dubious one. Every derived class for a specific OpenGL object type is going to have to define its own constructor and its own destructor functions. Furthermore, there is (almost) no function in the OpenGL API that can accept any kind of object. So any code that takes, for example, `Buffer` won't be able to take `Texture` or `VAO` or whatever. It would be inappropriate to pass a `TransformFeedback` object to a function that's expecting a `Buffer`.

So none of your APIs are going to be taking `GLObject` base class instances directly. They will always be using a specific derived class instance. As such, there is no polymorphism; the same operation cannot accept two different `GLObject` instances unless they use the same polymorphic type.

So what's the point of making them use a virtual base class at all?

Note that I said virtual base class. If you want to use a base class so that all of the various classes can share the (5 lines) of code for storing and retrieving the OpenGL object, that's fine. But that should use private inheritance, with a `using` directive to bring the private `ID` function to the public interface of the derived class. It should be an implementation detail of the class, not something that is exposed to the user.


i use a singelton "resource" class

Why is it a singleton? Is there some reason why you need to actively prevent users (like yourself) from creating multiple resource classes for managing textures? What if you need to have two sets of textures that are loaded and managed independently, so that you can flush all the textures from one set and load new ones, without disturbing the other managed set?

There is very little to be gained from making this class an explicit singleton. If the user wants to create multiple resource managers, so be it.