Search code examples
c++openglbindingtextures

Texture binding isn't working / C++ / OpenGL


I'm trying to create a Texture class for my project which initializes and load a texture from an image. The texture loads well but whenever I want to get the texture ID from outside the class by calling GetTexture() function, glIsTexture() does not consider the return value (the texture ID) as a texture anymore. And the face I want to texture stays blank.

Also, I tried to bind the texture with glBindTexture() directly from the Texture class itself with the function Texture::SetActive() but it still doesn't work.

And finally, when I return the texture ID directly from the function, the texture displays correctly.

Is there something I'm missing here ? I don't really know what to look for at this point.

Thanks in advance for your help !

Here's my Texture class :

// Constructor
Texture::Texture(std::string const& texPath) {

    SDL_Surface *texture = nullptr, *newFormatTexture = nullptr, *flippedTexture = nullptr;
    SDL_PixelFormat tmpFormat;
    Uint32 amask, rmask, gmask, bmask;

#if SDL_BYTEORDER == SDL_BIG_ENDIAN

    rmask = 0xFF000000;
    gmask = 0x00FF0000;
    bmask = 0x0000FF00;
    amask = 0x000000FF;

#else

    rmask = 0x000000FF;
    gmask = 0x0000FF00;
    bmask = 0x00FF0000;
    amask = 0xFF000000;

#endif

    if ((texture = IMG_Load(texPath.c_str())) == nullptr) {
        std::cerr << "[ERROR] : Could not load texture " << texPath << ". Skipping..." << std::endl;
    }

    tmpFormat = *(texture->format);
    tmpFormat.BitsPerPixel = 32;
    tmpFormat.BytesPerPixel = 4;
    tmpFormat.Rmask = rmask;
    tmpFormat.Gmask = gmask;
    tmpFormat.Bmask = bmask;
    tmpFormat.Amask = amask;

    if ((newFormatTexture = SDL_ConvertSurface(texture, &tmpFormat, SDL_SWSURFACE)) == nullptr)  {
        std::cerr << "[ERROR] : Couldn't convert surface to given format." << std::endl;
    }

    if ((flippedTexture = this->FlipSurface(newFormatTexture)) == nullptr) {
        std::cerr << "[ERROR] : Couldn't flip surface." << std::endl;
    }

    glGenTextures(1, &(this->_textureID));

    glBindTexture(GL_TEXTURE_2D, this->_textureID);

    glTexImage2D(GL_TEXTURE_2D, 0, 4, flippedTexture->w, flippedTexture->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, flippedTexture->pixels);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glBindTexture(GL_TEXTURE_2D, 0);

    SDL_FreeSurface(flippedTexture);
    SDL_FreeSurface(newFormatTexture);
    SDL_FreeSurface(texture);

}

Texture::Texture(unsigned char *texData, int width, int height) {

    glGenTextures(1, &(this->_textureID));
    glBindTexture(GL_TEXTURE_2D, this->_textureID);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, texData);
    glBindTexture(GL_TEXTURE_2D, 0);

}

Texture::~Texture() {
    glDeleteTextures(1, &(this->_textureID));
}

Texture Texture::CreateTexture(std::string const& texPath) {

     Texture tex(texPath);
     return (tex);
}

Texture Texture::CreateTexture(unsigned char *texData, int width, int height) {

    Texture tex(texData, width, height);
    return (tex);
}

unsigned int Texture::GetTexture() const {
    return (this->_textureID);
}

void Texture::SetActive() {
    glBindTexture(GL_TEXTURE_2D, this->_textureID);
}

The main class where I load and use my texture :

int WinMain(void) {

    Window window("Hello", 640, 480);
    double angleX, angleZ;
    Texture tex;
    int height;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70, (double)640/480, 1, 1000);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_DEPTH_TEST);

    tex = Texture::CreateTexture("caisse.jpg");

    while (!window.Quit()) {

        Input::Update();

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        gluLookAt(3,4,2,0,0,0,0,0,1);

        tex.SetActive();

        glBegin(GL_QUADS);

            glTexCoord2d(0, 1);
                 glVertex3d(1, 1, 1);

            glTexCoord2d(0, 0);
                 glVertex3d(1, 1, -1);

            glTexCoord2d(1, 0);
                 glVertex3d(-1, 1, -1);

            glTexCoord2d(1, 1);
                glVertex3d(-1, 1, 1);

        glEnd();

        glFlush();
        window.RefreshDisplay();
    }

    return (0);
}

EDIT

I solved my problem. As described in this topic : What are the usual troubleshooting steps for OpenGL textures not showing? , the initialisation of the texture must not be done in the constructor.

Thanks for the help :)


Solution

  • OK, let's look at this:

    Texture Texture::CreateTexture(std::string const& texPath) {
    
         Texture tex(texPath);
         return (tex);
    }
    

    I'm going to assume that this is a static function. So it creates a Texture object on the stack. And tex contains an OpenGL texture object. The function then returns this object.

    By the rules of C++, the lifetime of tex is limited to the scope in which it is created. Namely, Texture::CreateTexture. Which means that, at the end of this function, tex will be destroyed by having its destructor invoked.

    But since you returned tex, before that happens, tex will be used to initialize the return value of the function. That return value happens to be an object of type Texture, so the compiler will invoke Texture's copy constructor to initialize the return value.

    So, right before tex is destroyed, there are two Texture objects: tex itself and the return value of type Texture that was copied from tex. So far, so good.

    Now, tex is destroyed. Texture::~Texture calls glDestroyTexture on the texture object contained within it. That destroys the texture created in the constructor. Fine.

    So... what happens now? Well, let's back up to the creation of the return value from CreateTexture. I said that it would invoke the copy constructor of Texture to construct it, passing tex as the object to copy from.

    You did not post your complete code, but given the nature of the other code you've written, I'd bet that you didn't write a copy constructor for Texture. That's fine, because the compiler will make one for you.

    Only that's not fine. Why? Because right before tex gets destroyed, there are two Texture objects. And both of them store the same OpenGL texture object name. How did that happen?

    Because you copied the texture object from tex into the return value. That's what the compiler-generated copy constructor does: it copies everything in the class.

    So when tex is destroyed, it is destroying the OpenGL texture it just returned.

    Texture should not be a copyable class. It should be move-only, just like many resource-containing classes in C++.