Search code examples
c++openglsdl-ttf

SDL_TTF rendering garbage to texture


I am attempting to use OpenGL and SDL, using SDL_ttf to render text to a texture, but the code is rendering garbage.

My "Render to texture code":

GLuint textToTexture(std::string & text, TTF_Font* font, glm::vec4 textColour, glm::vec4 bgColour)
{
    if (!TTF_WasInit())
    {
        if (TTF_Init() == -1)
            exit(6);
    }
    SDL_Color colour = { (Uint8)(textColour.r*255), (Uint8)(textColour.g*255), (Uint8)(textColour.b*255), (Uint8)(textColour.a*255) };
    SDL_Color bg = { (Uint8)(bgColour.r*255), (Uint8)(bgColour.g*255), (Uint8)(bgColour.b*255), (Uint8)(bgColour.a*255) };

    SDL_Surface *stringImage = NULL;
    stringImage = TTF_RenderText_Blended(font, text.c_str(), colour);

    if (stringImage == NULL)
    {
        exit(5);
    }

    GLuint trueH = powerofTwo(stringImage->h);
    GLuint trueW = powerofTwo(stringImage->w);
    unsigned char* pixels = NULL;
    GLuint w = stringImage->w;
    GLuint h = stringImage->h;
    GLuint colours = stringImage->format->BytesPerPixel;
    pixels = padTexture((unsigned char*)stringImage->pixels, w, h, pixels, trueW, trueH, colours);
    GLuint format, internalFormat;
    if (colours == 4) {  

        if (stringImage->format->Rmask == 0x000000ff)
            format = GL_RGBA;
        else
            format = GL_BGRA;
    }
    else {      

        // no alpha
        if (stringImage->format->Rmask == 0x000000ff)
            format = GL_RGB;
        else
            format = GL_BGR;
    }
    internalFormat = (colours == 4) ? GL_RGBA : GL_RGB;


    GLuint texId = 0;
    //GLuint texture;

    glGenTextures(1, &texId);

    glBindTexture(GL_TEXTURE_2D, texId);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, trueW, trueH, 0,format, GL_UNSIGNED_BYTE, pixels);

    // SDL surface was used to generate the texture but is no longer
    // required. Release it to free memory
    SDL_FreeSurface(stringImage);
    free(pixels)
    return texId;
}

The code for computing the correct dimensions for padding:

int powerofTwo(int num)
{
    if (num != 0)
    {
        num--;
        num |= num >> 1;   // Divide by 2^k for consecutive doublings of k up to 32,
        num |= num >> 2;   // and then or the results.
        num |= num >> 4;
        num |= num >> 8;
        num |= num >> 16;
        num++;
    }
    return num;
}

and finally, the code that copies the bytes to a texture of the correct dimensions:

unsigned char* padTexture(unsigned char * src, int srcW, int srcH, unsigned char * dest, int width, int height, int bpp)
{
    dest = (unsigned char*)calloc(1, width*height*bpp);
    for (int i = 0; i < srcH; i++)
    {
        memcpy(dest + (width*i*bpp),src + (srcW*i*bpp), srcW*bpp);
    }
    return dest;
}

The result of this code is as follows: [![Garbled Texture][1]][1]

I have confirmed and error checked that SDL_TTF is properly initialized elsewhere in the codebase, and that the font is also being loaded. I have tested with three different ttf fonts, with the same results.

Also, if I use any other TTF_rendering function (Shaded, Solid etc), A solid quad is rendered, and the "colours" variable in the textToTexture function also ends up as 1.

Additional:

As I previously stated, I tested with three ttf fonts: MavenPro-Regular,

HelveticaNeueLTStd-Th

and another I found off the internet. I was trying to render the string "Select Scenario".

The pre padded image dimensions are 138x25 pixels.

The post padded image dimensions are 256x32 pixels.

Update 1:

After fixing the bpp issue the new texture is as follows:

New garbled

This image changes everytime I run the program.

Update 2: After fixing the additional spotted errors with padding the image, and setting the pixel data to the texture itself, when I use TTF_RenderText_Blended all I get is a black quad, and when I use TTF_RenderText_Shaded I get:

enter image description here

Update 3:

I used SDL_SaveBMP immedietly before calling the GL code and after calling SDL_RenderText_Blended, the result was a completely white image, (given which text colour).

When I do the same using TTF_RenderText_Solid, The saved image is as it should be, but is rendered by opengl like the images you see above.

SDL_TTF initialized fine, the fonts load without error, and the text rendering returns no errors, so I can't think what to do next.

Update 4:

I have since refactored all the ttf code into a single function and removed the padding code (as modern opengl doesn't seem to care about it). However, despite all project settings and code now being identical to a test project that is known to work on the same hardware, the problem persists.

GLuint textToTexture(const char * text, const char * font, glm::vec4 textColour, glm::vec4 bgColour, unsigned int & texID)
{
    if (!TTF_WasInit()) {
        if (TTF_Init() == -1)
            exit(6);
    }
    SDL_Color colour = { (Uint8)(textColour.r * 255), (Uint8)(textColour.g * 255), (Uint8)(textColour.b * 255),(Uint8)(textColour.a * 255) };
    SDL_Color bg = { (Uint8)(bgColour.r * 255), (Uint8)(bgColour.g * 255), (Uint8)(bgColour.b * 255),255 };
    TTF_Font* fontObj = TTF_OpenFont(font, 24);
    if (!fontObj)
    {
        SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
            "Texture Error",
            "Cannot load font to create texture.",
            NULL);
        return 0;
    }
    SDL_Surface *image = NULL;
    image = TTF_RenderText_Blended(fontObj, text, colour);
    if (image == NULL)
    {
        exit(5);
        //exitFatalError("String surface not created.");
        std::cout << "String surface not created." << std::endl;

    }
    unsigned char* pixels = NULL;
    GLuint w = image->w;
    GLuint h = image->h;
    GLuint colours = image->format->BytesPerPixel;
    GLuint externalFormat, internalFormat;
    SDL_PixelFormat *format = image->format;
    if (colours == 4) {

        if (image->format->Rmask == 0x000000ff)
            externalFormat = GL_RGBA;
        else
            externalFormat = GL_BGRA;
    }
    else {

        // no alpha
        if (image->format->Rmask == 0x000000ff)
            externalFormat = GL_RGB;
        else
            externalFormat = GL_BGR;
    }
    internalFormat = (colours == 4) ? GL_RGBA : GL_RGB;


    GLuint texId = 0;
    //GLuint texture;

    glGenTextures(1, &texID);

    glBindTexture(GL_TEXTURE_2D, texID);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, externalFormat, GL_UNSIGNED_BYTE, image->pixels);
    //glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, trueW, trueH, 0, externalFormat, GL_UNSIGNED_BYTE, pixels);
    glGenerateMipmap(GL_TEXTURE_2D);

    //// SDL surface was used to generate the texture but is no longer
    //// required. Release it to free memory
    SDL_FreeSurface(image);
    TTF_CloseFont(fontObj);
    return texID;
}

I have a workaround that saves the image to bmp, then reloads it and creates a texture, but only when I use TTF_RenderText_Shaded. If I use TTF_RenderText_Blended, I get an single colour image which corresponds to the text colour.


Solution

  • glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, trueH, trueW, 0,format, GL_UNSIGNED_BYTE, pixels);
    

    trueH and trueW order is reversed

    memcpy(src + (srcW*i*bpp), dest + (width*i*bpp), srcW*bpp);
    

    Source and destination order reversed.

    dest = (unsigned char*)calloc(0, width*height*bpp);
    

    0 elements of size width*height*bpp allocated, which is 0 bytes. Should be 1 instead of 0.

    Here is a complete example:

    #include <SDL2/SDL.h>
    #include <GL/gl.h>
    #include <SDL2/SDL_ttf.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    static unsigned char* padTexture(unsigned char * src, int srcW, int srcH, unsigned char * dest, int width, int height, int bpp, const SDL_Palette *palette)
    {
        int dst_bpp = (bpp == 1) ? 4 : bpp;
        dest = (unsigned char*)calloc(1, width*height*dst_bpp);
        if(bpp != 1) {
            for (int i = 0; i < srcH; i++)
            {
                memcpy(dest + (width*i*bpp), src + (srcW*i*bpp), srcW*bpp);
            }
        } else {
            /* indexed - read colours from palette */
            for(int i = 0; i < srcH; i++) {
                for(int j = 0; j < srcW; j++) {
                    memcpy(dest + (width*i+j)*dst_bpp,
                            &palette->colors[src[srcW*i+j]], sizeof(SDL_Color));
                }
            }
        }
        return dest;
    }
    
    static int powerofTwo(int num) {
        if (num != 0)
        {
            num--;
            num |= num >> 1;   // Divide by 2^k for consecutive doublings of k up to 32,
            num |= num >> 2;   // and then or the results.
            num |= num >> 4;
            num |= num >> 8;
            num |= num >> 16;
            num++;
        }
        return num;
    }
    
    static GLuint textToTexture(const char *text, TTF_Font* font) {
        if (!TTF_WasInit()) {
            if (TTF_Init() == -1)
                exit(6);
        }
        SDL_Color colour = { 255, 255, 255, 255 };
        SDL_Color bg = { 0, 0, 0, 255 };
    
        SDL_Surface *stringImage = NULL;
    //    stringImage = TTF_RenderText_Blended(font, text, colour);
        stringImage = TTF_RenderText_Shaded(font, text, colour, bg);
    
        if (stringImage == NULL) {
            exit(5);
        }
    
        GLuint trueH = powerofTwo(stringImage->h);
        GLuint trueW = powerofTwo(stringImage->w);
        unsigned char* pixels = NULL;
        GLuint w = stringImage->w;
        GLuint h = stringImage->h;
        GLuint colours = stringImage->format->BytesPerPixel;
        pixels = padTexture((unsigned char*)stringImage->pixels, w, h, pixels, trueW, trueH,
                colours, stringImage->format->palette);
        GLuint format, internalFormat;
    
        /* If indexed, want resulting image to be 32bit */
        if(colours == 1) {
            colours = 4;
        }
    
        if (colours == 4) {  
    
            if (stringImage->format->Rmask == 0x000000ff)
                format = GL_RGBA;
            else
                format = GL_BGRA;
        }
        else {      
    
            // no alpha
            if (stringImage->format->Rmask == 0x000000ff)
                format = GL_RGB;
            else
                format = GL_BGR;
        }
        internalFormat = (colours == 4) ? GL_RGBA : GL_RGB;
    
    
        GLuint texId = 0;
        //GLuint texture;
    
        glGenTextures(1, &texId);
    
        glBindTexture(GL_TEXTURE_2D, texId);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, trueW, trueH, 0,format, GL_UNSIGNED_BYTE, pixels);
    
        // SDL surface was used to generate the texture but is no longer
        // required. Release it to free memory
        SDL_FreeSurface(stringImage);
        free(pixels);
        return texId;
    }
    
    int main(int argc, char* argv[])
    {
        SDL_Init(SDL_INIT_VIDEO);
        TTF_Init();
    
        SDL_Window *window = SDL_CreateWindow("SDL2 Example", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 600, 400, SDL_WINDOW_OPENGL);
        SDL_GLContext gl_ctx = SDL_GL_CreateContext(window);
    
        TTF_Font *font = TTF_OpenFont(".fonts/tahoma.ttf", 16);
        if(font) {
            printf("font loaded\n");
            textToTexture("Select Scenario", font);
            TTF_CloseFont(font);
        }
    
        int quit = 0;
        while(!quit) {
            SDL_Event ev;
            while(SDL_PollEvent(&ev)) {
                if(ev.type == SDL_QUIT || ev.type == SDL_KEYUP) {
                    quit = 1;
                }
            }
    
            glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
            glLoadIdentity();
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glEnable(GL_TEXTURE_2D);
            glColor3f(1.0f, 1.0f, 1.0f);
            glBegin(GL_QUADS);
                glTexCoord2f(0, 1);
                glVertex2f(-0.5, -0.5);
                glTexCoord2f(0, 0);
                glVertex2f(-0.5, 0.5);
                glTexCoord2f(1, 0);
                glVertex2f(0.5, 0.5);
                glTexCoord2f(1, 1);
                glVertex2f(0.5, -0.5);
            glEnd();
    
            glFlush();
            SDL_GL_SwapWindow(window);
        }
    
        SDL_GL_DeleteContext(gl_ctx);
        SDL_DestroyWindow(window);
    
        TTF_Quit();
        SDL_Quit();
    
        return 0;
    }