Before we do that I need to point out some minor fixes which I made in one of the header files. In SDK/common/types.h the author defines the constants RAD_TO_DEG and DEG_TO_RAD. These, of course, are used to convert angles between radians and degrees. In the original version of the file RAD_TO_DEG is misdefined as "90.0f / M_PI"; I fixed the definition by changing it to "180.0f / M_PI". The other change I made to both RAD_TO_DEG and DEG_TO_RAD, was to enclose their expressions in parentheses. By using parentheses as part of the definition we prevent any problems, however unlikely, with operator precedence issues.
On to the remaining changes needed to successfully build and run the first sample program using our new SHADER and PROGRAM objects.
The new SHADER and PROGRAM classes both use the std::vector template. The line "#include <vector>" needs to be inserted somewhere to make this definition available everywhere the code might need it. I chose to add this include statement to the file SDK/common/gfx.h. This seems to be the location for including all of the miscellaneous include files needed by the modules.
The next files to update are SDK/common/font.cpp, SDK/common/navigation.cpp, and SDK/common/obj.cpp. The changes in these files all follow the same pattern:
- Change calls to PROGRAM_init() to "new PROGRAM".
- Change calls to SHADER_init() to "new SHADER".
- Change calls to SHADER_compile() to be invocations of the SHADER::compile() method.
- Change calls to PROGRAM_link() to be invocations of the PROGRAM::link() method.
- Change calls to PROGRAM_get_vertex_attrib_location() to be invocations of the PROGRAM::get_vertex_attrib_location() method.
- Change calls to PROGRAM_get_uniform_location() to be invocations of the PROGRAM::get_uniform_location() method.
- In FONT_free(), NAVIGATION_free(), and OBJ_free() delete calls to SHADER_free() and change the call to PROGRAM_free() to use delete to destruct the "PROGRAM *" in the FONT, NAVIGATION, and OBJ implementations, respectively. Remember that we no longer need to delete/free the SHADER objects separately because destructing a PROGRAM object will also implicitly destruct the PROGRAM object's SHADER objects.
While writing this post I observed that in both SDK/common/font.cpp and SDK/common/navigation.cpp that immediately after creating a new SHADER the code invokes the SHADER::compile() method on the newly created SHADER object. It probably makes sense to add a new SHADER constructor which combines these steps into a single method. We could also use this new constructor in the PROGRAM constructor which was created from the old PROGRAM_create() function. For example, this new constructor could be declared:
SHADER(const char *name,
GLenum shaderType, // GL_VERTEX_SHADER or
// GL_FRAGMENT_SHADER
const char *code,
bool debug); // Print error messages?
Alternatively/Additionally, we could go one step higher up the logic chain and add another PROGRAM constructor which takes a pointer to a char array which holds the GLSL code for the vertex shader, and a second pointer to a char array which holds GLSL code for the fragment shader. For example the PROGRAM constructor could be declared:
PROGRAM(const char *name, const char *vsCode,
const char *fsCode, bool debug);
This new PROGRAM constructor should also invoke, internally, the PROGRAM method link(); doing this automatically populates the uniform location map and the vertex attribute location map.
I may include these constructors in a later release of the code. If these constructors are useful for you in the interim, please feel free to implement them.
In the SDK/chapter2-1/templateApp.cpp file we make the same changes as described above in the bullet list for FONT, NAVIGATION, and OBJ modules. In addition we change the symbol DEBUG_SHADERS to be defined as true rather than as 1 (one); this, again, is a symptom of my aversion to "magic numbers". DEBUG_SHADERS is used as a boolean flag and not as a number; its definition should accurately reflect its usage.
SHADER(const char *name,
GLenum shaderType, // GL_VERTEX_SHADER or
// GL_FRAGMENT_SHADER
const char *code,
bool debug); // Print error messages?
Alternatively/Additionally, we could go one step higher up the logic chain and add another PROGRAM constructor which takes a pointer to a char array which holds the GLSL code for the vertex shader, and a second pointer to a char array which holds GLSL code for the fragment shader. For example the PROGRAM constructor could be declared:
PROGRAM(const char *name, const char *vsCode,
const char *fsCode, bool debug);
This new PROGRAM constructor should also invoke, internally, the PROGRAM method link(); doing this automatically populates the uniform location map and the vertex attribute location map.
I may include these constructors in a later release of the code. If these constructors are useful for you in the interim, please feel free to implement them.
In the SDK/chapter2-1/templateApp.cpp file we make the same changes as described above in the bullet list for FONT, NAVIGATION, and OBJ modules. In addition we change the symbol DEBUG_SHADERS to be defined as true rather than as 1 (one); this, again, is a symptom of my aversion to "magic numbers". DEBUG_SHADERS is used as a boolean flag and not as a number; its definition should accurately reflect its usage.
If you are following along making all of the changes to the code manually starting from the release tagged baseline you should be able to successfully build and execute the code. Please note that on the first pass through the code in the book I'm only targeting the iOS simulator running in the "iPhone Retina (3.5-inch)" mode.
Take a moment to note that in the function templateAppInit() we have redundant code. The function builds the vertex and fragment SHADERs and attaches them to the PROGRAM object. After attaching the SHADERs the code links the PROGRAM using the PROGRAM::link() method. This same logic is already implemented for us in the PROGRAM constructor which we made from the old PROGRAM_create() function. To see how the code can be simplified by using this alternate constructor you can pull the release tagged chapter2. When you pull this tagged release you'll also get working copies of the other two sample programs, chapter2-2 and chapter2-3, from chapter 2 of the book. Note that when the code uses this other constructor the test needed to check if the constructor failed changes. The old test was
if (!program->link(DEBUG_SHADERS)) {
and the new test is
if (program==NULL || program->pid==0) {
You may have noticed that there are modules used by the sample programs in chapter 2 which have not been modified to create C++ objects. They are the GFX and MEMORY modules. The GFX module doesn't present anything that constitutes an object. The GFX_start() function doesn't return an object and, consequently, there is no object to be passed into any of the subsequent GFX calls. Also, there are other things I'd like to change about the GFX module but I want to postpone that task until I've had a chance to introduce some additional tools.
Arguably, the MEMORY module could be made into an object class. If I did rewrite this module as a class I'd probably turn the mopen() and the mclose() functions into the class constructor and destructor, respectively. I would probably also rename the mread() and minsert() functions to become the read() and insert() class methods, respectively. There is no strong reason for failing to treat this module as an object class. My reason for not doing so is that, in spite of the modules name MEMORY, it really looks to me more like an I/O library and its C interfaces are consistent with that usage. If you disagree and think that the module should be written as an object class, please feel free to do so.
Regarding my recent posts one of my friends has pointed out that the tone of my posts have given the impression that I am a critic of the Mr. Marucchi‑Foino and/or his code. On the contrary, I have enjoyed the book greatly and was inspired by it. These posts about rewriting the various modules in the book to better exploit the capabilities of C++ are a result being inspired.
Hopefully, the following clarifies my position regarding author, the book, and the code presented in the book:
Where I have taken a different approach to solving the same problem from the methods used by the author the reader should take my comments only as a different point of view, not as a criticism of the author. Whether the author's solution to a particular problem or my alternate solution is better is often very context dependent. Alternate points of view shouldn't not be seen as conflict but rather an opportunity to learn how to see a problem from multiple perspectives. It's my hope that you, the reader, by contrasting the author's view with my view will be able to see additional ways of seeing the issues that neither the author nor myself envisioned.
In my next post I'll continue with chapter 3 but you knew that already, right? In particular, I'll begin work on making the OBJ data structure into a class. The OBJ data type is central to the remainder of the book. It's functions are used to read and display the contents of Wavefront data files.
Regarding my recent posts one of my friends has pointed out that the tone of my posts have given the impression that I am a critic of the Mr. Marucchi‑Foino and/or his code. On the contrary, I have enjoyed the book greatly and was inspired by it. These posts about rewriting the various modules in the book to better exploit the capabilities of C++ are a result being inspired.
Hopefully, the following clarifies my position regarding author, the book, and the code presented in the book:
- The code, as the author delivers it, is (IMHO) really C code not C++ code. I don't mean that to be a criticism of the code or the author. That isn't criticism or praise; it's simply my opinion after having examined the code.
- The code as written by the author has some advantages over my C++ makeover. Because the author's original code limits itself to using only C constructs game programmers who are working in C can easily benefit from the code. This is not true of the code as I have rewritten it; in order to use the code as I've rewritten it a C programmer would have to put C wrappers around everything and that adds extra overhead, both to the CPU and to the developer(s).
- The execution targets for this code are iOS and Android. Both of these are mobile platforms. Generally this means that there is limited CPU power, limited GPU power, limited memory space, and limited battery power. With these constraints in mind it's easier to for a good C compiler to get the last bit of performance out of a piece of code than it is for a C++ compiler. (In later posts I'll discuss some of the limitations of optimizing C++ that I encountered and how I tried to work around them.) Optimizing C++ (or any object oriented language) is a very hard problem because the compiler generates so much code "under the covers" that a novice programmer isn't always aware of all the overhead incurred supporting things like polymorphism. While working on one of the later sample programs I found that the CPU was spending 25% of its time in one of my overloaded operator functions; these sorts of problems are much easier to avoid, and, when they do occur, much easier to fix in C.
- While the author's intended execution targets are iOS and Android the person who introduced me to the book was easily able to get the code examples running on MacOS X. In my opinion this speaks well of the code. I expect that someone with good Linux or Windows skills could easily port the author's abstraction layer to those platforms as well, or perhaps even dedicated game consoles.
- I found the task of rewriting the author's code to proper C++ objects relatively easy. This is a testament to how well thought out the author's implementation of the code is. The author establishes a pattern which is followed pretty consistently thoughout the tools he presents. Though the author didn't really write in an object oriented programming language he clearly thought in object oriented terms. It was this cogent, well considered implementation, and the author's clear exposition of the material that inspired me to embark on the task of rewriting the code to be more fully exploit the capabilities of C++.
Where I have taken a different approach to solving the same problem from the methods used by the author the reader should take my comments only as a different point of view, not as a criticism of the author. Whether the author's solution to a particular problem or my alternate solution is better is often very context dependent. Alternate points of view shouldn't not be seen as conflict but rather an opportunity to learn how to see a problem from multiple perspectives. It's my hope that you, the reader, by contrasting the author's view with my view will be able to see additional ways of seeing the issues that neither the author nor myself envisioned.
In my next post I'll continue with chapter 3 but you knew that already, right? In particular, I'll begin work on making the OBJ data structure into a class. The OBJ data type is central to the remainder of the book. It's functions are used to read and display the contents of Wavefront data files.
No comments:
Post a Comment