1

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.

Ian Young
  • 1,712
  • 1
  • 16
  • 33
  • `src + (srcW*i)` - you need to multiply by bpp too (same goes for dst). Also you never freeing your pixels buffer, but it is unrelated to your current problem. – keltar Nov 15 '16 at 12:39
  • Ok I fixed that error(nicely spotted btw), but it made no improvement to the resultant texture, so is it possible there is some other issue? – Ian Young Nov 15 '16 at 22:49
  • Technically you need to align rows, but with bpp=4 they already are. Update question with changes you've made and result you're getting. It may also be helpful to know actual values of width and height of original surface, which font was used and what text is being rendered. – keltar Nov 16 '16 at 04:21

1 Answers1

2
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;
}
keltar
  • 17,711
  • 2
  • 37
  • 42
  • Nicely spotted. Corrected those errors but no discernible improvement, so I've updated the question with the new code. – Ian Young Nov 16 '16 at 19:10
  • 1
    Works fine for me. Make a complete example then. – keltar Nov 16 '16 at 19:35
  • I've checked on the data stored in GPU ram and the image is garbled there too, so I'm guessing the issue is either with SDL_TTF itself, or with copying the data to the GPU. Which version of SDL_TTF are you using? – Ian Young Nov 17 '16 at 12:27
  • @IanYoung SDL 2.0.5, SDL2-ttf 2.0.14. Not that it matters. I've also added a complete example (check font path though). – keltar Nov 17 '16 at 13:53
  • 1
    Interesting. I built your code and it runs fine. I guess there is something else going on... – Ian Young Nov 17 '16 at 14:33
  • I'm still having the issue so I was playing around with the code in your complete example, and interestingly, if I use 'TTF_RenderText_Shaded', or 'TTF_RenderText_Solid' then both produced images are at best distorted or at worst, completely garbled. if all those functions work on your system then I think it's possible that there is an issue on mine. Could you possibly check that please? – Ian Young Nov 21 '16 at 10:40
  • 1
    @IanYoung these functions documentation says they create 8bit palette surface (which is correct, by the way). Palette is a colour table, and each pixel specified as index in this table, so it is 1 byte per pixel, but total number of colours in image limited by 256; you cannot directly copy index as it isn't a colour itself. I've updated code sample with one of possible ways to handle palette; another (and, probably, easier) method would be creating new surface for target size and blitting source into it. – keltar Nov 21 '16 at 14:26
  • That's awesome, my bad for not reading the docs properly. I've updated my code, to match, however one final problem remains: Sometimes the text looks fine, sometimes it's skewed. I'm looking into it now, but If you can think of why that would be in the meantime, please let me know. Thanks. I'll add that I figured out why the blended text wasn't working too. As the blended text is supposed to have a transparent background, it's the same colour as the foreground, but with differing alpha values. As my shaders only access the RGB components of the texture, it was being rendered as all white. – Ian Young Nov 22 '16 at 09:49
  • The extent of the skewing of the final texture seems to be proportional to the length of the string being rendered. – Ian Young Nov 22 '16 at 10:07
  • That could be due to pitch, I haven't handled it properly in my example. Check if surface's `pitch` != `width`*`BytesPerPixel`. Or GL don't like unaligned rows (could be disabled). Grab actual surface parameters first. – keltar Nov 22 '16 at 11:15
  • You're quite correct again. The pitch is different. – Ian Young Nov 22 '16 at 11:41
  • I just had to change `memcpy(dest + (srcW*i + j) * 4, &palette->colors[src[srcW*i + j]], sizeof(SDL_Color));` to `memcpy(dest + (srcW*i + j) * 4, &palette->colors[src[pitch*i + j]], sizeof(SDL_Color));` Everything works great now. Thank you for all your help! – Ian Young Nov 22 '16 at 13:16