5

I asked a question the other day, about rendering TTF fonts using SDL, and was pointed towards SDL_TTFL I've tried using the SDL_TTF library, but All I'm getting is garbage on screen

I have included my shaders, which are very simple for this program, and also the snipped I'm using to load the text into surface, and to bind it to the texture. I'm not trying to do anything crazy here at all. Is there anything I'm doing wrong you can see? I'm not really too sure how to debug shaders etc.

Fragment Shader (frag.glsl):

#version 330
in vec2 texCoord;
in vec4 fragColor;

out vec3 finalColor;

uniform sampler2D myTextureSampler;
void main() {
    finalColor = texture( myTextureSampler, texCoord ).rgb;
}

Vertex Shader (vert.glsl)

#version 330

in vec3 vert;
in vec4 color;
in vec2 texcoord;

out vec4 fragColor;
out vec2 texCoord;

void main() {
    fragColor = color;
    gl_Position = vec4(vert, 1);
    texCoord = texcoord;
}

Font Loading (loadFont.cpp)

//Initialise TTF
if( TTF_Init() == -1 )
    throw std::runtime_error("SDL_TTF failed to initialise.");

//Load the texture
font = TTF_OpenFont( filePath.c_str(), 12 );
if(!font)
    throw std::runtime_error("Couldn't load: "+ filePath);

TTF_SetFontStyle(font, TTF_STYLE_NORMAL);

surface = TTF_RenderUTF8_Blended(font, "Hello", this->textColor);
Uint8 colors = surface->format->BytesPerPixel;
int texture_format;
if (colors == 4) {   // alpha
    if (surface->format->Rmask == 0x000000ff)
        texture_format = GL_RGBA;
    else
        texture_format = GL_BGRA;
} else {             // no alpha
    if (surface->format->Rmask == 0x000000ff)
        texture_format = GL_RGB;
    else
        texture_format = GL_BGR;
}
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

glTexImage2D(GL_TEXTURE_2D, 0, colors, surface->w, surface->h, 0,
             texture_format, GL_UNSIGNED_BYTE, surface->pixels);
SDL_FreeSurface(surface);

Vertex Attribute Setup

GLfloat vertices[] = {
    //X    Y      Z     R     G     B     A     U    V
    -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.f, 1.f,
     1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 1.f, 1.f,
    -1.0f, -0.4f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.f, 0.f,
     1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 1.f, 1.f,
     1.0f, -0.4f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 1.f, 0.f,
    -1.0f, -0.4f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.f, 0.f

};


glGenVertexArrays(1, &_vao);
glBindVertexArray(_vao);

glGenBuffers(1, &_vbo);
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glEnableVertexAttribArray(program->attrib("vert"));
glVertexAttribPointer(program->attrib("vert"), 3, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), NULL);

glEnableVertexAttribArray(program->attrib("color"));
glVertexAttribPointer(program->attrib("color"), 4, GL_FLOAT, GL_TRUE,  9*sizeof(GLfloat), (const GLvoid*)(3 * sizeof(GLfloat)));

glEnableVertexAttribArray(program->attrib("texcoord"));
glVertexAttribPointer(program->attrib("texcoord"), 2, GL_FLOAT, GL_TRUE,  9*sizeof(GLfloat), (const GLvoid*)(7 * sizeof(GLfloat)));

I've attached the code I'm using for the vertex attributes as per the comment below.

EDIT: In a reply that has been deleted since, It was asked whether SDL_TTF was returning 3 or 4 channels. It's returning a BGRA image. I've tried changing my fragment shader to

Fragment shader

#version 330
in vec2 texCoord;
in vec4 fragColor;

out vec4 finalColor;

uniform sampler2D myTextureSampler;
void main() {
    finalColor = texture( myTextureSampler, texCoord ).rgba;
}

Note the vec4, and using rgba rather than rgb. This just leads to a black rectangle. I also tried generating a surface using SDL_LoadBMP(), which gives the exact same results.

Will P
  • 192
  • 1
  • 9
  • @AndonM.Coleman damn you, I just did you the exact same thing. – vallentin Nov 03 '13 at 00:26
  • 2
    for future questions please added your code to the question itself and not in an external service, because if your code is removed from the external service, the question won't be for any future use for people having a similar problem. – vallentin Nov 03 '13 at 00:27
  • @Vallentin: Yeah, I am very sorry about that. We basically performed the same edits at the same time. Had you suggested them earlier I would have approved them rather than doing them myself. – Andon M. Coleman Nov 03 '13 at 00:29
  • Sorry! I was trying to keep my question as succinct as possible – Will P Nov 03 '13 at 00:32
  • @AndonM.Coleman No need to be sorry, would have approved yours as well buddy. – vallentin Nov 03 '13 at 01:15
  • Can you add the code where you setup your vertex attrib pointers to the question? I can't imagine what exactly is causing this perlin noise looking effect off the top of my head, but maybe a little bit more code would help clarify things. Right now I'm suspecting either pixel transfer issues in your `glTexImage2D (...)` call or invalid vertex attrib pointer setup. – Andon M. Coleman Nov 03 '13 at 03:01
  • Sure, I've added the code above. – Will P Nov 03 '13 at 11:52
  • Does using a different texture render correctly? If so it's something to do with creating a texture from the SDL surface. If not, something's messed up in the OpenGL code. – user673679 Nov 03 '13 at 12:01
  • In additon to @user673679: does your image change when you use `RenderUTF8_Solid`? – Jongware Nov 03 '13 at 12:42
  • Image is the same when I use RenderUTF8_Solid. I'm swapping to load an image as a texture now. – Will P Nov 03 '13 at 15:50
  • I'm using SDL_LoadBMP and binding to a texture using the same code as above, but still facing the same problem :/ – Will P Nov 03 '13 at 16:00
  • This isn't exactly what your looking for, but I have a working implementation here : https://github.com/Xonar/FRAME. Look at GUI/Font and Engine/FontEngine. (It's the start of a Font Engine) You can cross reference your code with mine. I didn't do exactly what you did, but I used the SDL_TTF to populate a GL_TEXTURE_2D_ARRAY and then populate the vertex data with the text I wanted to render. If by tomorrow there isn't a solution I'll see if I can get your specific case to work. – Xonar Nov 04 '13 at 08:02
  • thanks, I'll take a look at that tomorrow and see what I can do with it! – Will P Nov 05 '13 at 22:05

2 Answers2

3

Your call to

glTexImage2D(GL_TEXTURE_2D, 0, colors, surface->w, surface->h, 0, texture_format, GL_UNSIGNED_BYTE, surface->pixels);

Is a problem.

The third paramter is wrong:

http://www.opengl.org/sdk/docs/man/xhtml/glTexImage2D.xml

internalFormat

                Specifies the number of color components in the texture.
                Must be one of base internal formats given in Table 1,
                one of the sized internal formats given in Table 2, or one
                of the compressed internal formats given in Table 3, below.

I suspect you want yours to be GL_RGBA (or what format you want opengl to store your texture in)

EDIT:

I just saw it now, but you are using only 3 channels in your fragment shader. The Blended function requires that you use 4 channels otherwise the alpha channel is going to be messed up.

I think your "main" problem lies somewhere else though as that should just make the colour constant over the entire surface. (Not the "garbage" you are seeing)

I quickly wrote this program that mostly does what your doing. I think it will help you more than my repository as it's straight to the point.

#include <GL/glew.h>

#include <SDL2/SDL.h>
#include <SDL2/SDL_opengl.h>
#include <SDL2/SDL_ttf.h>

#include <string>
#include <iostream>

using namespace std;

SDL_Window *window = NULL;
SDL_GLContext context = NULL;
TTF_Font* font = NULL;
SDL_Surface* surface = NULL;

//OpenGL Objects
GLuint vao;
GLuint vbo;
GLuint texture;

//Shader Objects
GLuint program;
GLuint vs;
GLuint fs;

//Sampler Object
GLuint uniformSampler;

//Callback Function
APIENTRY GLvoid debugMessageCallbackFunction( GLenum source, GLenum type, GLuint id, GLenum severity,
                                 GLsizei length, const GLchar* message, GLvoid* userParam)
{
  cerr << endl << "\t" << message << endl;
}

//The shaders are identical to yours
const string fragmentShaderString = 
                "#version 130\n" // My laptop can't do OpenGL 3.3 so 3.0 will have to do
                "in vec2 texCoord;\n"
                "in vec4 fragColor;\n"
                "\n"
                "out vec4 finalColor;\n"
                "\n"
                "uniform sampler2D myTextureSampler;\n"
                "void main() {\n"
                "  finalColor = texture( myTextureSampler, texCoord ) * fragColor;\n"
                "}";

const string vertexShaderString = 
                "#version 130\n"
                "\n"
                "in vec3 vert;\n"
                "in vec4 color;\n"
                "in vec2 texcoord;\n"
                "\n"
                "out vec4 fragColor;\n"
                "out vec2 texCoord;\n"

                "void main() {\n"
                "  fragColor = color;\n"
                "  gl_Position = vec4(vert, 1);\n"
                "  texCoord = texcoord;\n"
                "}\n";

//Your vertices, but I changed alpha to 1.0f
const GLfloat vertices[] = 
{
    //X    Y      Z     R     G     B     A     U    V
    -1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.f, 1.f,
     1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.f, 1.f,
    -1.0f, -0.4f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.f, 0.f,
     1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.f, 1.f,
     1.0f, -0.4f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.f, 0.f,
    -1.0f, -0.4f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.f, 0.f
};

int main(int argc, char* args[])
{
  //Create Window and Context
  window = SDL_CreateWindow("SDL Text with OpenGL", 0, 0, 640, 480, SDL_WINDOW_OPENGL);

  //Set Core Context
  SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 );
  SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 1 );
  SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

  context = SDL_GL_CreateContext(window);

  //Simple OpenGL State Settings
  glViewport( 0.f, 0.f, 640.f, 480.f);
  glClearColor( 0.f, 0.f, 0.f, 1.f);

  //Init Glew
  //Set glewExperimental for Core Context
  glewExperimental=true;
  glewInit();

  //Set Blending 
  //Required so that the alpha channels show up from the surface
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  //Simple callback function for GL errors
  glDebugMessageCallbackARB(debugMessageCallbackFunction, NULL);

  //Create Shaders
  vs = glCreateShader(GL_VERTEX_SHADER);
  fs = glCreateShader(GL_FRAGMENT_SHADER);

  //Source Pointers
  const GLchar* vsSource= &vertexShaderString[0];
  const GLchar* fsSource = &fragmentShaderString[0];

  //Set Source
  glShaderSource(vs, 1, &vsSource, NULL);
  glShaderSource(fs, 1, &fsSource, NULL);

  //Compile Shaders
  glCompileShader(fs);
  glCompileShader(vs);

  //Create Shader Program
  program = glCreateProgram();

  //Attach Shaders to Program
  glAttachShader(program, vs);
  glAttachShader(program, fs);

  //No need for shaders anymore
  glDeleteShader(vs);
  glDeleteShader(fs);

  //Set Attribute Locations
  glBindAttribLocation(program, 0, "vert");
  glBindAttribLocation(program, 1, "color");
  glBindAttribLocation(program, 2, "texcoord");

  //Link Program
  glLinkProgram(program);

  //Setup VAO and VBO
  glGenVertexArrays(1, &vao);
  glGenBuffers(1, &vbo);

  glBindVertexArray(vao);
  glBindBuffer(GL_ARRAY_BUFFER, vbo);

  glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 9 * 6, vertices, GL_STATIC_DRAW);

  glEnableVertexAttribArray(0);
  glEnableVertexAttribArray(1);
  glEnableVertexAttribArray(2);

  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), NULL);
  glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat),(GLvoid*)(3*sizeof(GLfloat)));
  glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat),(GLvoid*)(7*sizeof(GLfloat)));

  //Init TTF
  TTF_Init();

  //Open Font
  font = TTF_OpenFont("DroidSansFallbackFull.ttf", 30);

  SDL_Color color = {255, 255, 255, 255};

  //Create Surface
  surface = TTF_RenderUTF8_Blended(font, "This is TEXT!", color);

  //Your format checker
  GLenum format = (surface->format->BytesPerPixel==3)?GL_RGB:GL_RGBA;

  //Create OpenGL Texture
  glGenTextures(1, &texture);
  glBindTexture(GL_TEXTURE_2D, texture);
  glTexImage2D( GL_TEXTURE_2D, 0, format, surface->w, surface->h, 0, 
              format, GL_UNSIGNED_BYTE, surface->pixels);

  //Set Some basic parameters
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

  //Set up Sampler
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, texture);

  uniformSampler = glGetUniformLocation(program, "myTextureSampler");
  //It defaults to using GL_TEXTURE0, so it's not necessary to set it
  //in this program it's generally a good idea.

  //-------------------------------------------------------------------------------------- 
  // DRAW STAGE
  //-------------------------------------------------------------------------------------- 

  glUseProgram(program);

  //glBindVertexArray(vao); - still in use

  glClear(GL_COLOR_BUFFER_BIT);

  glDrawArrays(GL_TRIANGLES, 0, 6);

  SDL_GL_SwapWindow(window);

  //Sleep for 2s before closing
  SDL_Delay(2000);
}

I didn't do any error checking or close any of the resources since it's just meant to be a reference and not meant to be used.

Usually I don't use glew, but writing code to manually get the functions for such a small program seemed pointless.

It compiles with

g++ source.cpp -g -lSDL2 -lSDL2_ttf -lGL -GLEW -o demo

on linux. You might need to make some adjustments for Windows (Headers files might change slightly and libraries will change as wel) and I think it will work without change on Mac.

EDIT 2:

To compile it on windows with mingw you need to add APIENTRY to callback function and the main should have arguments. Changed code to reflect this.

Tested it and it works on both windows and linux. (Provided that your implementation have access to the GL_ARB_debug_callback extension, if not just comment that out)

Xonar
  • 1,296
  • 1
  • 14
  • 21
  • I swapped colors to be GL_BGRA, (which is what texture_format should be) but no joy. Still the same problem :/ – Will P Nov 05 '13 at 22:04
  • The code that you have there look correct as far as I can tell. You can also still bind some texture parameter, but I doubt that is the cause of your problem. I also found that in shader attribute index specification is generally easier to manage than continually asking what index is that attrib bound to (For me at least), but that's just preference. Are you sure your binding your sampler correctly? You can check that the data sent to the GPU is correct by drawing the texcoords and locations as colors. – Xonar Nov 06 '13 at 10:25
  • So I looked at your sample code and seemed fairly simple, but what you said about checking my sampler seems to have led me down the right road. I Tried doing it, and I got the same noise, but in a different colour. Digging down, I pulled out all the data from my VBO except vertices, and tried colouring them red, with no joy. I rewrote my shader, (just a basic one, take in vertices and output the colour) and I'm building it back up. I have no idea what the actual issue is, but I'll keep you posted. Thanks for the help! – Will P Nov 10 '13 at 03:03
  • I've edited my shader, and rewrote it. I now get a black rectangle. – Will P Nov 10 '13 at 03:14
  • @WillP I added a example program in an edit. If your still struggling, maybe you can put of more of your code. – Xonar Nov 11 '13 at 12:48
  • Woo! Finally! It runs on windows, but not on mac. You need glewExperimental = GL_TRUE; for it to run, but still get the same issue. Guess that's a bug with SDL_TTF. – maccard Nov 12 '13 at 00:08
  • @maccard you only need glewExperimental if your creating a core context. I found no bugs in SDL_TTF so far (under Windows and Linux at least). You can additionally explicitly specify that the sampler should use GL_TEXTURE0. I still can't reproduce the error with the garbled output. – Xonar Nov 12 '13 at 08:46
  • Sorry, commented under a roommates account last time. I can't get SDL_TTF to work with the core context, which is disappointing. – Will P Nov 13 '13 at 17:27
  • @WillP I changed it to work with Core Context. It works flawlessly under my Linux Machine. Can you test my sample and report if you still have garbled text output. It doesn't make a difference to SDL_TTF if the context is core or not so I don't think it's a problem with SDL_TTF. (glew perhaps) – Xonar Nov 13 '13 at 18:14
  • I did! Works on Core profile, and on all platforms. Even managed to get it integrated with my own code from your example. Turns out, I get that nasty black rectangle if I render it on top of another textured rectangle without disabling my depth buffer for the text. Overall, I'm not really sure why it wouldn't work, but it's working now. Thanks so much for your help! And apologies for the delay, this is only a play project for me, so I don't have endless time to spend on it. – Will P Nov 17 '13 at 23:22
0

Does work nicely, only got to edit the const GLfloat vertices[] array to be able to change the text color consistently. For a solid color text, have all RGB components in the array equal to 1.0f and render the texture in color. For a multicolored text, first render the texture in white with SDL_Color color = { 255, 255, 255, 255 };, then edit the array as shown here below.

float width = (float)surface->w;
float height = (float)surface->h;

// alpha to 1.0f
const GLfloat vertices[] = {
    // X         Y          Z     R     G     B     A     U     V
    -1.0, -height / width, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
    1.0f, -height / width, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
    -1.0f, height / width, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
    1.0f, -height / width, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
    1.0f,  height / width, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
    -1.0f, height / width, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f
};

Multi-colored text rendered with SDL as texture in OpenGL

LastBlow
  • 617
  • 9
  • 16