Palm’s WebOS PDK

The objective of this post is to simplify the “Simple” 3D program from Palm for WebOS’s PDK.

Back in 1998 or so I took a Graphics Programming class at SUNY Fredonia. It had way too much linear algebra in it. I had no idea how to do math with matrices. Matrix Math may sound like a boring sequel to a Keanu Reaves movie but is the underlying engine to convert 3D objects to a 2D picture i.e. Your computer screen.

Thankfully, when it came to programming you didn’t need to worry about that kind of thing usually.

With the full version of OpenGL and the Glut library it was easy to create 3D objects add lighting and move the objects around.

When the PDK came out for WebOS I was excited. Maybe I should recreate my final project from Graphics Programming class (Boxing Bunnies, 3D cartoon bunnies that punched each other).

I downloaded the PDK and got the “Simple” example to run super easily in Visual Studio. “SWEET!” I thought, “Let’s look at the code and start expanding it”.

BLAM! I was lost. Programs to “Shade” both “Vertices and Fragments”… Each Vertex labeled along with a tons of things called “Faces”. Where are my rectangles? Where are my complex flat shapes, easily created spheres or light sources?

I came to find out that OpenGL ES doesn’t have any of those libraries to make that stuff. OpenGL ES is meant to be extremely small and take up the least amount of resources possible.

The only thing that you can draw is the simplest shape possible. A Triangle! Every other shape is made from them. Furthermore, what the heck is a vertex shader or a fragment shader? Why do I need 2 different shaders? A vertex is just a point in space, so how can I shade one?

So I kept walking away from the PDK and OpenGL ES then would pop my head back in. I even got the OpenGL ES 2 0 Programming Guide and was even more confused. Every example online references OpenGL ES 1 (which is very different from 2.0) or it used libraries to create shapes that aren’t included in the PDK.

Well I eventually found some things out and have made a simple rotating triangle program based on Simple from Palm. I’ve added some comments so the code makes more sense. Each comment begins with “// Geiger – …” so you can spot my comments.

I haven’t gotten into describing shaders so much yet but plan to figure out the rest of their details and explain them in PLAIN English.

If you replace the Simple.cpp code from Palm with the code below, you can play with the spinning Triangle. I will have more to say, especially about Shaders, soon.

Download the Text File Here

/**
    JayGeiger.com – I’ve added more comments so the program makes more sense.
    I will get into more details, especially Shaders in future blog posts.
**/
#include <stdio.h>
#include <math.h>

#include <GLES2/gl2.h>
#include "SDL.h"

SDL_Surface *Surface;               // Screen surface to retrieve width/height information

int         Shader[2];              // We have a vertex & a fragment shader
int         Program;                // Totalling one program
float       Angle = 0.0;            // Rotation angle of our object
float       Proj[4][4];             // Projection matrix
int         iProj, iModel;          // Our 2 uniforms

#ifdef WIN32
extern "C"
#endif
GL_API int GL_APIENTRY _dgles_load_library(void *, void *(*)(void *, const char *));

static void *proc_loader(void *h, const char *name)
{
    (void) h;
    return SDL_GL_GetProcAddress(name);
}

// Standard GL perspective matrix creation
void Persp(float Proj[4][4], const float FOV, const float ZNear, const float ZFar)
{
    const float Delta   = ZFar – ZNear;

    memset(Proj, 0, sizeof(Proj));

    Proj[0][0] = 1.0f / tanf(FOV * 3.1415926535f / 360.0f);
    Proj[1][1] = Proj[0][0] / ((float)Surface->h / Surface->w);

    Proj[2][2] = -(ZFar + ZNear) / Delta;
    Proj[2][3] = -1.0f;
    Proj[3][2] = -2.0f * ZFar * ZNear / Delta;
}

// Simple function to create a shader
void LoadShader(char *Code, int ID)
{
    // Compile the shader code
    glShaderSource  (ID, 1, (const char **)&Code, NULL);
    glCompileShader (ID);

    // Verify that it worked
    int ShaderStatus;
    glGetShaderiv(ID, GL_COMPILE_STATUS, &ShaderStatus);

    // Check the compile status
    if (ShaderStatus != GL_TRUE) {
        printf("Error: Failed to compile GLSL program\n");
        int Len = 1024;
        char Error[1024];
        glGetShaderInfoLog(ID, 1024, &Len, Error);
        printf(Error);
        exit (-1);
    }
}

// Initializes the application data
int Init(void)
{
    // Very basic ambient+diffusion model
    // Geiger – A vertex shader doesn’t really shade shapes.  It’s more like a transformation of the points that make up the shape.
    //   I WILL HAVE MORE TO SAY ABOUT THIS AT JAYGEIGER.COM LATER
    const char VertexShader[] = "                   \
        attribute vec3 Position;                    \
        attribute vec3 Normal;                      \
                                                    \
        uniform mat4 Proj;                          \
        uniform mat4 Model;                         \
                                                    \
        varying vec3 NormVec;                       \
        varying vec3 LighVec;                       \
                                                    \
        void main(void)                             \
        {                                           \
            vec4 Pos = Model * vec4(Position, 1.0); \
                                                    \
            gl_Position = Proj * Pos;               \
                                                    \
            NormVec     = (Model * vec4(Normal,0.0)).xyz;     \
            LighVec     = -Pos.xyz;                 \
        }                                           \
    ";

    const char FragmentShader[] = "                                             \
        varying highp vec3 NormVec;                                             \
        varying highp vec3 LighVec;                                             \
                                                                                \
        void main(void)                                                         \
        {                                                                       \
            lowp vec3 Color = vec3(1.0, 0.0, 0.0);                              \
                                                                                \
            mediump vec3 Norm  = normalize(NormVec);                            \
            mediump vec3 Light = normalize(LighVec);                            \
                                                                                \
            mediump float Diffuse = dot(Norm, Light);                           \
                                                                                \
            gl_FragColor = vec4(Color * (max(Diffuse, 0.0) * 0.6 + 0.4), 0.5);  \
        }                                                                       \
    ";

    // Create 2 shader programs
    Shader[0] = glCreateShader(GL_VERTEX_SHADER);
    Shader[1] = glCreateShader(GL_FRAGMENT_SHADER);

    LoadShader((char *)VertexShader, Shader[0]);
    LoadShader((char *)FragmentShader, Shader[1]);

    // Create the prorgam and attach the shaders & attributes
    Program   = glCreateProgram();

    glAttachShader(Program, Shader[0]);
    glAttachShader(Program, Shader[1]);

    glBindAttribLocation(Program, 0, "Position");
    glBindAttribLocation(Program, 1, "Normal");

    // Link
    glLinkProgram(Program);

    // Validate our work thus far
    int ShaderStatus;
    glGetProgramiv(Program, GL_LINK_STATUS, &ShaderStatus);

    if (ShaderStatus != GL_TRUE) {
        printf("Error: Failed to link GLSL program\n");
        int Len = 1024;
        char Error[1024];
        glGetProgramInfoLog(Program, 1024, &Len, Error);
        printf(Error);
        exit(-1);
    }

    glValidateProgram(Program);

    glGetProgramiv(Program, GL_VALIDATE_STATUS, &ShaderStatus);

    if (ShaderStatus != GL_TRUE) {
        printf("Error: Failed to validate GLSL program\n");
        exit(-1);
    }

    // Enable the program
    glUseProgram                (Program);
    glEnableVertexAttribArray   (0);
    glEnableVertexAttribArray   (1);

    // Setup the Projection matrix
    Persp(Proj, 70.0f, 0.1f, 200.0f);

    // Retrieve our uniforms
    iProj   = glGetUniformLocation(Program, "Proj");
    iModel  = glGetUniformLocation(Program, "Model");

    // Basic GL setup
    glClearColor    (0.0, 0.0, 0.0, 1.0);
    glEnable        (GL_CULL_FACE);
    glCullFace      (GL_BACK);

    return GL_TRUE;
}

// Main-loop workhorse function for displaying the object
void Display(void)
{
    // Clear the screen
    glClear (GL_COLOR_BUFFER_BIT);

    float Model[4][4];

    memset(Model, 0, sizeof(Model));

    // Setup the Proj so that the object rotates around the Y axis
    // We’ll also translate it appropriately to Display
    Model[0][0] = cosf(Angle);
    Model[1][1] = 1.0f;
    Model[2][0] = sinf(Angle);
    Model[0][2] = -sinf(Angle);
    Model[2][2] = cos(Angle);
    Model[3][2] = -1.0f;  
    Model[3][3] = 1.0f;

    // Constantly rotate the object as a function of time
    // Geiger – This simply creates an increasing angle of rotation.  SDL keeps increasing (or ticking).
    Angle = SDL_GetTicks() * 0.001f;

    // Vertex information
    // Geiger – This is a simple triangle.  The coordinates are in X,Y,Z space.  Notice that Z is always the same.
    float PtData[][3] = {
        {-0.5, 0.5, 0.5},
        {0.5, 0.5, 0.5},
        {-0.5, -0.5, 0.5},
    };

    // Face information
    // Geiger – Each Shape has a face
    unsigned int FaceData[][3] = {
        {0,1,2}, //1 side of the Triangle
        {2,1,0} //The other side of the triangle.  If you comment this line out and rerun then the triangle will invisible on one side.
    };

    // Draw the icosahedron
    // Geiger – This draws the triangle (not the icosahedron)
    glUseProgram            (Program);
    glUniformMatrix4fv      (iProj, 1, false, (const float *)&Proj[0][0]);
    glUniformMatrix4fv      (iModel, 1, false, (const float *)&Model[0][0]);

    glVertexAttribPointer   (0, 3, GL_FLOAT, 0, 0, &PtData[0][0]);
    glVertexAttribPointer   (1, 3, GL_FLOAT, GL_TRUE, 0, &PtData[0][0]);

    glDrawElements          (GL_TRIANGLES, sizeof(FaceData) / sizeof(int), GL_UNSIGNED_INT, &FaceData[0][0]);
}

int main(int argc, char** argv)
{
    // Initialize the SDL library with the Video subsystem
    // Geiger – SDL stands for Simple DirectMedia Layer.  SDL can be thought of the programs that allow OpenGL ES to render the images to your screen.
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);

    // Tell it to use OpenGL version 2.0
    // Geiger – To be more correct, this is OpenGL ES 2.0
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);

    // Set the video mode to full screen with OpenGL-ES support
    // Geiger – This is the full screen resolution of the Palm Pre.  I am not sure how things will be handled for multiple resolutions ie… Pixi.
    Surface = SDL_SetVideoMode(320, 480, 0, SDL_OPENGL);

#if WIN32
    // Load the desktop OpenGL-ES emulation library
    _dgles_load_library(NULL, proc_loader);
#endif

    // Application specific Initialize of data structures & GL states
    if (Init() == false)
        return -1;

    // Event descriptor
    // Geiger – The Event variable will hold and SDL Event. Some examples of these kind of events are KeyDown – A specifc key press, Mouse clicks, etc…
    SDL_Event Event;

    do {
        // Render our scene
        // Geiger – This method will rotate the 3D Object and display it.  More detail in the above function.
        Display();

        // Make it visible on the screen
        /* Geiger – Graphics Programs always use 2 buffers.  The screen always shows one of these buffers.  The inactive one gets painted with all the pixels for the next frame.
            The Display() method always draws to the inactive buffer.  After Display() is done drawing all the pixels the inactive buffer becomes the active and vice versa.
            Why does it do this?  If it drew the pixels right to the screen things would "Flicker".  It would be kind of like a cartoonist drawing a picture really fast, showing it for a second
            then turning to a blank page and drawing the next one.  Double Buffering hides the flicker by "Flipping" to the next page.
        */
        SDL_GL_SwapBuffers();

        // Process the events
        // Geiger – SDL_PollEvent will remove the next SDL Event and save it to the "Event" variable.  This will keep happening, and the "Display" function will keep rotating and redisplaying the object.
        while (SDL_PollEvent(&Event)) {
            switch (Event.type) {
                // List of keys that have been pressed
                case SDL_KEYDOWN:
                    // Geiger – If the last event was the pressing of the "Escape Key", then break out of the "Do While" loop.
                    switch (Event.key.keysym.sym) {
                        // Escape forces us to quit the app
                        case SDLK_ESCAPE:
                            Event.type = SDL_QUIT;
                            break;

                        default:
                            break;
                    }
                    break;

                default:
                    break;
            }
        }

    } while (Event.type != SDL_QUIT);
    // We exit anytime we get a request to quit the app

    // Cleanup
    SDL_Quit();

    return 0;
}