The main function

Like in the other apps we have created in this chapter, our main function will need to initialize both SDL and OpenGL variables. The beginning of the main function is the same as it was at the beginning of our glow app. It initializes SDL, then compiles and links the OpenGL shaders and creates a new OpenGL program:

int main() {
SDL_Init( SDL_INIT_VIDEO );
SDL_CreateWindowAndRenderer( CANVAS_WIDTH, CANVAS_HEIGHT, 0,
&window, &renderer );
SDL_SetRenderDrawColor( renderer, 0, 0, 0, 255 );
SDL_RenderClear( renderer );

GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);

glShaderSource( vertex_shader,
1,
vertex_shader_code,
0);

glCompileShader(vertex_shader);

GLint compile_success = 0;
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &compile_success);

if(compile_success == GL_FALSE)
{
printf("failed to compile vertex shader\n");
glDeleteShader(vertex_shader);
return 0;
}

GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);

glShaderSource( fragment_shader,
1,
fragment_shader_code,
0);

glCompileShader(fragment_shader);
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS,
&compile_success);

if(compile_success == GL_FALSE)
{
printf("failed to compile fragment shader\n");

GLint maxLength = 0;
glGetShaderiv(fragment_shader, GL_INFO_LOG_LENGTH, &maxLength);

GLchar* errorLog = malloc(maxLength);
glGetShaderInfoLog(fragment_shader, maxLength, &maxLength,
&errorLog[0]);
printf("error: %s\n", errorLog);

glDeleteShader(fragment_shader);
return 0;
}

program = glCreateProgram();
glAttachShader( program,
vertex_shader);

glAttachShader( program,
fragment_shader);

glLinkProgram(program);

GLint link_success = 0;

glGetProgramiv(program, GL_LINK_STATUS, &link_success);

if (link_success == GL_FALSE)
{
printf("failed to link program\n");
glDeleteProgram(program);
return 0;
}

glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
glUseProgram(program);

After initializing SDL and creating the OpenGL shader program, we need to get uniform variable references for our OpenGL shader program. Two of these references are new to this version of the program. The u_normal_location variable will be a reference to the u_normal sampler uniform variable, and the u_light_pos_location variable will be a reference to the u_light_pos uniform variable. Here is the new version of our references:

u_texture_location = glGetUniformLocation(program, "u_texture");
u_normal_location = glGetUniformLocation(program, "u_normal");
u_light_pos_location = glGetUniformLocation(program, "u_light_pos");
u_translate_location = glGetUniformLocation(program, "u_translate");

After grabbing the references to our uniform variables, we need to do the same for our attributes:

a_position_location = glGetAttribLocation(program, "a_position");
a_texcoord_location = glGetAttribLocation(program, "a_texcoord");

We then need to generate the vertex buffer, bind it, and buffer the data from the array we created earlier. This should be the same code that we had in the glow.c file:

glGenBuffers(1, &vertex_texture_buffer);

glBindBuffer( GL_ARRAY_BUFFER, vertex_texture_buffer );
glBufferData( GL_ARRAY_BUFFER, sizeof(vertex_texture_data),
vertex_texture_data, GL_STATIC_DRAW);

Next, we will need to set up all of our textures. Two of them will be rendered using OpenGL, while the other will be rendered using SDL. Here is the initialization code for all three of the textures:

glGenTextures( 1,
&circle_tex);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, circle_tex);

surface = IMG_Load( "/sprites/circle.png" );
if( !surface ) {
printf("failed to load image: %s\n", IMG_GetError() );
return 0;
}

glTexImage2D( GL_TEXTURE_2D,
0,
GL_RGBA,
128, // sprite width
128, // sprite height
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
surface );

glUniform1i(u_texture_location, 1);
glGenerateMipmap(GL_TEXTURE_2D);

SDL_FreeSurface( surface );

glGenTextures( 1,
&normal_tex);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, normal_tex);

surface = IMG_Load( "/sprites/ball-normal.png" );

if( !surface ) {
printf("failed to load image: %s\n", IMG_GetError() );
return 0;
}

glTexImage2D( GL_TEXTURE_2D,
0,
GL_RGBA,
128, // sprite width
128, // sprite height
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
surface );

glUniform1i(u_normal_location, 1);
glGenerateMipmap(GL_TEXTURE_2D);

SDL_FreeSurface( surface );

surface = IMG_Load( "/sprites/light.png" );

if( !surface ) {
printf("failed to load image: %s\n", IMG_GetError() );
return 0;
}

light_texture = SDL_CreateTextureFromSurface( renderer, surface );

if( !light_texture ) {
printf("failed to create light texture: %s\n", IMG_GetError() );
return 0;
}

SDL_QueryTexture( light_texture,
NULL, NULL,
&light_width, &light_height );

SDL_FreeSurface( surface );

This is a fairly large block of code, so let me walk through it a piece at a time. The first three lines generate, activate, and bind the circle texture so that we can begin to update it:

glGenTextures( 1,
&circle_tex);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, circle_tex);

Now that we have the circle texture ready to update, we can load the image file using SDL:

surface = IMG_Load( "/sprites/circle.png" );

if( !surface ) {
printf("failed to load image: %s\n", IMG_GetError() );
return 0;
}

Next, we need to load that data into our bound texture:

glTexImage2D( GL_TEXTURE_2D,
0,
GL_RGBA,
128, // sprite width
128, // sprite height
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
surface );

Then, we can activate that texture, generate mipmaps, and free the surface:

glUniform1i(u_texture_location, 1);
glGenerateMipmap(GL_TEXTURE_2D);

SDL_FreeSurface( surface );

After doing this for our circle texture, we need to do the same series of steps for our normal map:

glGenTextures( 1,
&normal_tex);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, normal_tex);
surface = IMG_Load( "/sprites/ball-normal.png" );

if( !surface ) {
printf("failed to load image: %s\n", IMG_GetError() );
return 0;
}

glTexImage2D( GL_TEXTURE_2D,
0,
GL_RGBA,
128, // sprite width
128, // sprite height
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
surface );

glUniform1i(u_normal_location, 1);
glGenerateMipmap(GL_TEXTURE_2D);

SDL_FreeSurface( surface );

We will handle the final texture differently because it will only be rendered using SDL. This should be pretty familiar to you by now. We need to load the surface from the image file, create a texture from the surface, query the size of that texture, and then free the original surface:

surface = IMG_Load( "/sprites/light.png" );

if( !surface ) {
printf("failed to load image: %s\n", IMG_GetError() );
return 0;
}

light_texture = SDL_CreateTextureFromSurface( renderer, surface );

if( !light_texture ) {
printf("failed to create light texture: %s\n", IMG_GetError() );
return 0;
}

SDL_QueryTexture( light_texture,
NULL, NULL,
&light_width, &light_height );

SDL_FreeSurface( surface );

Now that we have created our textures, we should set up our alpha blending:

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);

The last line of our main function uses Emscripten to call the game loop:

emscripten_set_main_loop(game_loop, 0, 0);