Tuesday, October 29, 2013

Turning the PROGRAM Type into an Object

This post continues the discussion of rewriting various modules from the book Game and Graphics Programming for iOS and Android with OpenGL ES 2.0 by Romain Marucchi‑Foino.  In this post I'll cover how I rewrote the PROGRAM data type.

Before going on I should clarify one point.  It's common for writers to refer to themselves in the third person as "the author".  In these blog posts regarding this book the term "the author" refers to Mr. Marucchi‑Foino.  When speaking of myself in these posts I will use personal pronouns (I, me, my, mine, etc.).

Some of the changes to the PROGRAM data type are similar to changes made in the last post about the SHADER data type:
  • change the declaration of the PROGRAM data type from being a typedef to a struct,
  • take note of places where unsigned char variables are used to transmit boolean values and change the variables to be type bool,
  • create a constructor from the PROGRAM_init() function,
  • add initializer lists to the constructors to initialize data members to zero/NULL/false, as appropriate, for data members without an explicit value passed in the argument list of the constructor,
  • retype various data members with appropriate OpenGL data types, and
  • create a destructor from the PROGRAM_free() function.
The old PROGRAM_create() function became one of two constructors for the new PROGRAM class.  Since the old PROGRAM_create() function also called the old PROGRAM_init() function to perform some initialization on its behalf, the old PROGRAM_init() function actually becomes two methods in the implementation of the new PROGRAM class: the first is a private init() method, and the second a public constructor.  Both constructors call the init() method to perform some of their initialization.  In future posts there will be similar examples of the old THING_init() function spawning both a private THING::init() function and a constructor.

In the constructor created from the old PROGRAM_create() the code explicitly initializes the programdrawcallback and programbindattribcallback data members; the new code initializes these data members, along with the data member pid, using an initializer list so we can remove their explicit initialization from the body of the constructor.

The constructor created from PROGRAM_create() also allocates and initializes two SHADERs, a vertex shader and a fragment shader.  The initialization steps are changed to use the new object oriented SHADER methods (the changes to the load_gfx() method parallel these changes in this constructor).  Because PROGRAM data objects can have their own SHADER objects my opinion is that the destructor should, correspondingly, have code which releases the SHADERs should they exist.  For this reason new code is added to the destructor which checks to see if the PROGRAM has initialized its vertex_shader and fragment_shader data members, and, if they exist, deletes them.  Admittedly, there is some risk here.  The programmer could separately create SHADER objects and manually set the vertex_shader and/or fragment_shader data members of a PROGRAM object to point to these separately created SHADER objects.  After having done this the programmer could retain and reuse his own pointers to the SHADER objects.  Deleting the SHADER objects in the PROGRAM destructor would leave these private copies of the object addresses pointing at invalid addresses.  In the future we can use C++ shared_ptrs to protect us from releasing SHADER objects which are still referenced other places but in the short term I'll manage the problem manually.

The PROGRAM data type also has two arrays which it manages.  In the old code each array had a count (uniform_count and vertex_attrib_count) and a pointer (uniform_array and vertex_attrib_array) to space which was dynamically allocated/reallocated to allow the array to grow, as needed.  In the new implementation these two arrays are replaced with vectors from the Standard Template Library (STL).  Since the code now uses vectors the PROGRAM data type no longer needs the array counts (uniform_count and vertex_attrib_count).

Remember that C (and C++ by way of its C origins) allows us to syntactically interchange pointers for arrays and vice versa.  Because of this while I discuss the uniform_array data member I will use the two terms, pointer and array, somewhat interchangeably.  In the PROGRAM_add_uniform() function, as each new shader uniform variable is added, the uniform_array has its space reallocated to insure that the array has enough space for the new entry.  Since the new version of the code declares uniform_array to be a vector we can no longer use realloc(3) to add elements to the uniform_array.  Instead we use the std::vector methods size() to retrieve the current number of elements in the vector and resize() to increase the number of elements in the vector by 1 (one).  After increasing the vector size each of the data fields in the new UNIFORM element of the vector is individually initialized.  Arguably, we should have been able to accomplish that task more neatly using the std::vector method push_back().  You should feel free to change the code but shortly I will discuss a change to the code which will make even the use of push_back() unnecessary.

Since uniform_array is no longer a pointer to dynamically allocated space we need to change the destructor so that uniform_array is no longer freed (free(3)).  Instead the code uses the std::vector method resize() to release the space.

The changes to vertex_attrib_array parallel the changes made when using uniform_array making their discussion redundant.

These two vectors/arrays (uniform_array and vertex_attrib_array) are populated using a while loop.  In my opinion, almost every case where the author uses a while loop and a counter (usually i), the code would be better implemented using an iterated for loop. Let's look at the original loop for populating the uniform_array:

    i = 0;
    while( i != total )
    {
glGetActiveUniform( program->pid,
                         i,
                         MAX_CHAR,
                         &len,
                         &size,
                         &type,
                         name );

       PROGRAM_add_uniform( program, name, type );

       ++i;
    }

In the new code the loop becomes:

    for (int i=0; i != total; ++i) {
  glGetActiveUniform(this->pid,
                           i,
                           MAX_CHAR,
                           &len,
                           &size,
                           &type,
                           name);

this->add_uniform(name, type);
    }

Since the initialization, test, and increment controlling the loop are all together in one place I think this makes the code easier to read, though others may think differently.

As I was writing this post I realized that the old storage and retrieval methods of the location of uniform variables and vertex attribute variables really don't exploit the capabilities of C++.  All of the location data are being stored as an array and each time we need to look up the location of a name we have to linearly search through the array.  Having realized this I replaced the vectors (arrays) with associative containers, i.e. maps, from the STL; which is why, as discussed above, the use of push_back() becomes unnecessary.  Let's look at how to replace uniform_array with a map.  I started by removing the name data member from the UNIFORM type:

    typedef struct
    {
  GLenum  type;
GLint  location;
bool    constant;
    } UNIFORM;

and I replaced the declaration of the PROGRAM data member uniform_array with

    std::map<std::string,UNIFORM>    uniform_map;

The method for inserting new UNIFORM variables in the map became:

    void PROGRAM::add_uniform(char *name, GLenum type)
    {
        this->uniform_map[name].type = type;
        this->uniform_map[name].location =
            glGetUniformLocation(this->pid, name);
        this->uniform_map[name].constant = false;
    }

Note that this method no longer has a return value.  The old method returned the "array index" of the newly added UNIFORM element.  Since we're no longer using a linear data structure returning an index isn't meaningful.  Remember this also means we need to change the definition of the method in the header file.

The method for retrieving a (shader) uniform variable's location is now:

    GLint PROGRAM::get_uniform_location(char *name)
    {
        auto it = uniform_map.find(name);

        return it==uniform_map.end() ?
            static_cast<GLint>(-1) :
            it->second->location;
    }

If you compare these two methods to the versions using std::vector<UNIFORM> I hope you'll agree that they are simpler.  One other side benefit is that the code no longer allocates a fixed size area for the name of the uniform variable; this should prevent buffer overflow problems.

Likewise, for managing vertex attribute information I 
  • deleted the name field from the VERTEX_ATTRIB data structure,
  • replaced vertex_attrib_array with vertex_attrib_map, again using std::string as a key to do the lookup, and
  • modified the add_vertex_attrib() and get_vertex_attrib_location() methods accordingly.
C++ supports several map types.  A map can be either ordered or unordered, and require unique keys or allow duplicate keys.  For the task of storing the locations of uniform and vertex attribute variables we need to use a map which requires unique keys.  But it's not necessary we use an ordered map type like the type I chose above.  I haven't benchmarked the mapping code; it may be that the unordered_map type is faster.  There are at least four areas of performance we might be concerned with:

  • the speed of insertion of new entries,
  • the speed of looking up a particular entry,
  • the speed of iterating over the entire map, and/or
  • the amount of memory needed to implement the map (iOS and Android are both mobile operating systems and may be memory constrained).

Different applications may consider the performance of these areas of different levels of importance.  In the various sample applications in the book the maps are only created once and then used, unchanged, for the life of the application so the first criterion probably isn't important for the sample programs.  Only the most simplistic of the sample programs look up individual entries by name; in those cases the second criterion is probably the most important.  The more complicated sample programs repeatedly iterate over all of the values in the map.  I haven't done any benchmarking on any of these criteria so I can't offer any guidance on whether map or unordered_map gives better performance for application use cases.  My choice of whether to use map or unordered_map was influenced by how many characters I needed to type and that I can rarely remember the naming conventions for the various associative container subtypes.  Hence, I chose map since its name is the shortest and comes readily to mind.  You may wish to use a more rigorous method of choosing which type you use to solve similar problems.

Even though my latest version of the code no longer uses std::vectors for the PROGRAM object type the discussion of converting a count and a pointer into a std::vector is still relevant because various other modules in the book require a similar technique and in those instances the data do need to be arranged as a linear data structure.  When addressing the changes to those modules in future posts I will skip the discussion of changing a count and an array pointer into an std::vector since the topic has already been covered in this post.

If you have looked at the header file you may have noticed that the add_uniform(), and add_vertex_attrib() methods are now declared private.  The add_uniform() and add_vertex_attrib() methods are only ever used by other methods of PROGRAM so they have been hidden from view.  Also, note that the destructor uses the std::map method clear() to free the space; we could have (and should have) used clear() in the std::vector version of the code too, rather than resize(0).

Because I have already committed the old code which uses vectors the new code using maps won't be available using the tag chapter2‑1 but the changes will be available eventually.

Since the modified methods for managing the UNIFORM and VERTEX_ATTRIB data structures now use std::string instead of "char[]"/"char*" you might be wondering why I haven't made this change more universally throughout the code.  Arguably I should but
  • the task's priority hasn't risen high enough on my "to do" list;
  • if I'm going to change the way strings are stored I want to consider supporting more than just ASCII (or even Latin 1, aka ISO/IEC 8859-1) strings; I want to consider if the string type should be UTF‑8*, UTF‑16,  Unicode, or some other encoding and I haven't spent enough time researching the tradeoffs and the compatibility of these various encodings with the various data files a graphics application may need to process (such as Wavefront, Blender, Ogg Vorbis, and MD5 files which are covered later in the book), and what string encodings can be used to access files with names in non‑English/non‑Western European languages; and
  • many of these data structures contain data which came from an OpenGL function, and/or must be passed into an OpenGL function and since OpenGL is a C API the data passed into and out of the OpenGL functions must be held in C data types (which may mean that string is a suitable substitute for "char[]"/"char*" because the string type has the c_str() method but data types which support more than ASCII and/or Latin 1 may not be suitable).
* My prejudice is to use UTF‑8 because another project (which I hope to blog about in the future) uses UTF‑8 encoding, and because if a string has only ASCII characters its UTF‑8 encoding is identical to its ASCII encoding.

One last thing.  We aren't done adding methods to the PROGRAM object type.  In a future post I'll discuss one of the functions which is part of another module but is really (IMHO) a PROGRAM method.

In my next post I'll talk about the changes in the main application file, SDK/chapter2‑1/templateApp.cpp, and the changes needed in other modules to use the modified versions of the SHADER and PROGRAM code.

Thursday, October 24, 2013

Creating Object-Oriented Tools for Game Development


When possible I attend the local NSCoders night and CocoaHeads meetings.  At one of these meetings I was introduced to the book Game and Graphics Programming for iOS and Android with OpenGL ES 2.0 published by Wrox.  The author, Romain Marucchi-Foino, presents a series of lessons regarding different aspects of writing games.  There are several things I like about this book.
  • The first is that the author has created an abstraction layer which insulates the programmer from whether the game is being run on iOS or Android.  This abstraction layer allows the programmer to write in C++.
  • In addition to the abstraction layer the author has provided a number of modules for managing various tasks such as sound, threads, programming the GPU via OpenGL, etc.
  • The book also introduces a number of subjects which I had been curious about such as physics, path calculation, and various OpenGL shader techniques.
  • The topics are presented in an orderly fashion and I found the book really useful for learning how to use OpenGL for game development.
On the other hand there are several things about the code where I think there's room for improvement.  While the code is purported to be C++ code, to my way of thinking it's really C code which has been compiled using a C++ compiler.
  • The various modules could easily have been implemented as proper C++ classes
  • Most of the memory management is done using traditional C malloc(3) methods instead of new, delete, and the Standard Template Library.
  • C++ classes could have been used to define vector, matrix, and quaternion types to exploit operator overloading making the equations (IMHO) more readable.
  • No benefit is derived from C++ type checking.
Over the course of some number of posts I'm going to discuss the various modifications I've made to the author's original code to leverage the benefits of C++.  Along the way I'll point out some of the opportunities to optimize the code.

It's my assumption that the reader has the book and can get an explanation of the author's software tools from the author himself.  Also, these posts assume that the reader is familiar with OpenGL.  This series of posts will only focus on the changes I've made to the code.

I've created a source code repository at GitHub.  To pull a copy of the code as it would be written by the author use the tag baseline.  To get the code as I've rewritten it to use objects for the SHADER and PROGRAM data types use the tag chapter2-1.

The first sample program, chapter2-1, briefly introduces how to draw a simple polygon on the screen using the PROGRAM, and SHADER data types.  Since the SHADER type is simpler we'll start there.

Before getting into the details of the changes made to the SHADER data type a brief overview of the procedure for performing the rewrites is in order.  Generally, each of the author's header files thing.h declares the type THING as follows:

    typedef struct
    {
        ...
    } THING;

I'm going to change these various declarations to look like

    struct THING {
        ...
    };

Also, the thing.h header will usually declare THING_init() and/or THING_create() functions.  I use these functions to create one or more constructors for the THING class.

If there is a THING_free() function I remake it into the class destructor.  If this function is missing I'll have to build a destructor from scratch.

The remaining miscellaneous functions, such as THING_something(THING *, ...) will become class methods named something(...).

Look at the file SDK/common/shader.h.  There are four functions which all have names starting with "SHADER_" and most of them pass a "SHADER *" as the first argument.
The SHADER class declaration becomes

    struct SHADER {
        char            name[ MAX_CHAR ];
        unsigned int    type;
        unsigned int    sid;
    public:
        SHADER(char *name, unsigned int type);
        ~SHADER();
        unsigned char compile(const char *code,
                              unsigned char debug);
        void delete_id();
    };

So, for the SHADER data type the function SHADER_init() becomes the class constructor, retaining its original parameter list and SHADER_free() becomes the class destructor but we no longer explicitly pass in a pointer to the object we're going to free; inside the destructor we can reference the SHADER being destructed using this.

The functions SHADER_compile() and SHADER_delete_id() become the class methods compile() and delete_id(), respectively.  In addition, the first argument is removed from each of their argument lists because in our implementation code we'll be able to use this to reference the SHADER object, just like in the constructor/destructor methods.

We're not quite done with the changes to our header file.  The implementation file, SDK/common/shader.cpp, shows us that some of the data types we used in our modified header file don't accurately represent how the data is used.  For example, the author's SHADER_compile() function passes the type data member to glCreateShader().  If you look at the man page for glCreateShader() you'll see that the argument which we're supposed to pass has the type GLenum.  This implies that the type data member is more correctly declared to be type GLenum.  This also has implications for our constructor.  The type argument should also be declared to be of type GLenum.

When we look to see how the sid data member is used we can see that data member sid ought to be declared to be type GLuint.   This doesn't have any impact on the signatures of our other member functions because sid is never passed in an argument list.

There is, however, one other argument list that needs (IMHO) to change.  We ought to declare the argument debug for the member function compile() to be of type bool.  The author's code which calls the original function SHADER_compile() just passes either 0 (zero) or 1 (one).  I don't like this programming style because the zero or the one doesn't really tell you what it means, i.e., the zero/one could be used by the function as a numeric value or as a true/false (Boolean) value.  We don't know without looking at the implementation of SHADER_compile().  This defeats the data hiding we would normally want to do when writing object oriented code.  When I modify the various places which call the compile() method I'll explicitly pass in either true or false as the debug parameter making it clear to the reader how the value passed to the debug parameter is being used, numerically vs. logically.  Also note that the return value for compile() should be bool; its return statements have been changed to return true/false, accordingly.

[Note:  Yes, I know that C/C++ can use zero/non-zero idiomatically for true/false; I still think this is easier to read.]

Our final declaration of the SHADER class is

    struct SHADER {
        char      name[ MAX_CHAR ];
        GLenum    type;
        GLuint    sid;
    public:
        SHADER(char *name, GLenum type);
        ~SHADER();
        bool compile(const char *code, bool debug);
        void delete_id();
    };

We can finally begin working on the implementation file, SDK/common/shader.cpp.  The code in SHADER_init() needed to allocate space for the new SHADER object and the allocation was done using calloc(3) which automatically sets all of the memory to zero.  We no longer need to allocate the space.  We'll be creating SHADER objects using the new operator.  When our constructor is invoked the space will already have been allocated.  The old SHADER_init() implementation took two arguments: name and type. We can initialize the type and sid data members outside of the body of the function by using an initializer list.  The data member type can/will be initialized from the constructor argument type but what value do we use to initialize sid?  Remember that in the original SHADER_init() function the code allocated space for the SHADER data structure using calloc(3).  The calloc() call sets all of the data fields to zero so that is the value we'll use to initialize sid.  The last thing we need to initialize in the constructor is the name data member.  name is a fixed sized data field.  To protect ourselves against buffer overflow errors I'll use assert(3) to make sure that the name argument passed into the constructor will fit into the name data field in the SHADER object.  We could also use assert(3)(but I don't) to verify that the type value passed into the constructor was GL_VERTEX_SHADER or GL_FRAGMENT_SHADER; in the future we could also choose to allow GL_GEOMETRY_SHADER but none of the author's sample programs use geometry shaders.

In the destructor method we release the shader ID using the delete_id() method.  We should keep track of the delete_id() method.  Once we've implemented all of the example code we may find that we can hide the method from users, that is, we may be able to redeclare the method to be private; I probably wouldn't choose to make the method protected because the SHADER class is never subclassed.  Since this is a destructor we no longer need to free(3)the memory; the compiler takes care of that for us.

Since we've already talked about the delete_id() method we might as well cover it next.  You'll see that it's pretty trivial.  If a shader ID has been allocated we release it using glDeleteShader().  Note that since we're conditionally releasing the shader ID I've already removed the guard logic that used to exist in the old SHADER_free() function from the destructor method. The guard logic in the SHADER_free() kept us from calling SHADER_delete_id() if the data member sid was zero; we don't need to test for this condition (sid == 0) in both places, i.e., in the destructor and in the delete_id() method.

The compile() method should be straight forward to readers who are familiar with programmable shaders in OpenGL.  Basically the method
  • generates a shader ID,
  • associates the shader's source code with its ID,
  • attempts to compile the shader code,
  • optionally prints any failure messages which are generated,
  • if compilation fails the shader ID is released, and
  • the method returns whether or not the attempt to compile the shader code was successful.
If you're unsure what any of this means you'll need to brush up on your OpenGL knowledge.

The truly compulsive can define the loglen, and status local variables to be of type Glint, and the local variable log to be of type "GLchar *" then cast:
  • loglen to type size_t when it's passed into malloc(),
  • the return value of malloc() to be "GLchar *",
  • loglen to type GLsizei when it's passed into glGetShaderInfoLog(), and
  • &loglen to type "GLsizei *" when it's passed into glGetShaderInfoLog().
[Note:  I may or may not continue to be so compulsive in future code examples.]

That's enough for now.  I'll discuss the changes needed to implement PROGRAM as a full C++ class in the next post.

Sunday, October 13, 2013

It's awkward first blog post introduction time!

Q: What is this blog about?
A:  Sharing OpenGL code that I've written or modified.

Q: What topics will be covered?
A: Any programming topic which captures my interest.  This does mean that from time to time I may veer off the blog's stated purpose but I'm most interested in discussing 3D graphics code in the context of writing real applications.

Q: What is my background?
A:  My formal education is as a mathematician.  In my professional life I'm a software engineer*.  I got my first taste of writing computer graphics code over 25 years ago experimenting with assembly code for CGA, EGA, VGA, and Hercules graphics cards.  From there I went on to working in C on an X terminal project.  After that I cofounded a graphics card company which made products for Sun workstations.  The whole world of computer graphics has gone 3D so in an effort to remain relevant I started learning OpenGL over 14 years ago.  After all this time I'm still disappointed that the project for which I learned OpenGL was cancelled but "c'est la vie/guerre".  For me, the good news is that working in 3D graphics "scratches some of my itches":
  • As a mathematician it's hard to share with friends and family how excited I am about various mathematical insights as I acquire them but when I run a graphics demo for people of the work I'm doing I can see the light in their eyes and it's really gratifying.
  • I get to
    • use my math knowledge†,
    • sharpen my object oriented programming skills in C++, and
    • draw pictures which appeals to the aesthete‡ in me.
* I always think of myself professionally as a computer programmer and feel like the job title "software engineer" is a bit pretentious.  Still, if one wants to be taken seriously professionally one does what one must!

 Or "maths" knowledge for my British readers. Forgive me please; I'm hopelessly American. ☺

‡ While doing my undergraduate work I earned a minor in music.  In my personal life I'm a fan of social dancing (for those who care international standard is my favorite style and I'm currently learning Argentine tango) and the art museums of Europe.

Q:  Will your blog be the graphics programming analog of "world peace" or the "unified field theory of relativity"?
A:  No, probably not but
  • I find that explaining what I've done gives me clarity and helps me form a consistent (programming) philosophical framework for what I've done and how the tasks should be done.  In this respect the blog will be a lab diary for my own benefit.
  • The things I've learned may be of use to others in their own development.  Hopefully, I'll provide a different perspective or way of explaining the concepts which will be beneficial even if what I have to say isn't ground breaking from a technology standpoint.
Q:  Will the blog be math intensive?
A:  I hope not.  Mostly, I expect it will be "theory of programming" intensive.  Still, I don't want to make promises I may not be able to keep.  In order to help clarify the way the code works I will occasionally explain what the various computations mean but I don't expect to spend any time on proofs.  I really expect to spend more time explaining why I wrote a particular piece of software to do a computation the way I did.  As issues come up I do expect to provide a bibliography for readers who want to dig deeper into the mathematical theory.

Q:  What platform(s) will the code run on?
A:  Primarily Mac OS X and/or iOS.  In some cases I plan to use GLUT or OpenGLUT for the code which runs on the Mac which, hopefully, will keep the code portable to other programming environments. Still, I won't guarantee all of the code will be portable to other platforms, as written.

Q:  Do I blather on this much in person, too?
A:  Sadly, yes.

Q: When will I actually post some code?
A:  Real Soon Now™!

The last little bit of housekeeping I'll offer is that comments and criticisms are welcome.  As I said earlier in this post using different words this blog is partly a learning experience for me and I'm looking forward to learning from readers' comments.