Deconstructing the OpenGL Pipeline in illiTorpot

\begin{document} \maketitle \section{Introduction} This lesson teases the OpenGL pipeline structure from the code for illiTorpot, which itself is a development of the standard illiSkel. The latter has been the standard template code for almost all C/OpenGL projects for illiMath in the decade 1994-2006. No complete illiSkel exists at this time for OpenGL/Python, although it should be straightforward to develop the with the missing functionality. \subsection{Prerequisites} The reader should be able to read C-code at the beginner's level, and should have access to a way of recompiling skel.cpp and torpot.cpp on their computer. On a PC running Windows, the torpot.cpp adapted for Windows compiles with the native Windows compiler \texit{mvc98} (provided in the illiMath Winaid package) using the \textit{} makefile filter. On a mac, the \textit{} will recompile the unix adapted torpot.cpp. The filter has been adapted in the past to a variety of Linux distros as \texit{}, but these are very specific to the particular way your flavor of Linux handles the graphics system on your PC. \subsection{Torpot versus Skel} The illiSkel, shortened to skel.c, skel.cpp, etc and compiled to just \texttt{skel} in Unix, and to \texttt{ skel.exe} in Windows, has a simple pipeline, whose deconstruction we leave to the reader as an exercise in understanding the present lesson. \subsection{Versions} On Windows PCs, use repo:
with mvc98 or other Windows compilers (modify to suit). The commandline entry for mvc98, reads:
\texttt{nmake -f skel.exe }
On a Mac (at least through OSX10.6) use repo:
\texttt{class198f12/resources/illimac/skel/skel.c} and
The commandline entry on a prepared mac reads:
\texttt{make -f torpot}.
\section{Analysis of the Code} \subsection{Where do we start the deconstruction?} The function \texttt{ drawcons()} must be the display master for two reasons: \begin{itemize} \item The command \texttt{ swapbuffers()} is called at its very end. This signals OpenGL to switch display buffers at the next opportunity. \item The overall master function \texttt{ main()} identifies drawcons() as the \textit{ display callback}. \end{itemize} So we start we always start with the \texttt{main()} function. If you understood the terms in this introduction, skip the following explanantion of them. OpenGL specifies that your graphics card is \textit{double buffered.}That means, OpenGL draws data (pixel color, transparency etc) into one display buffer, while the other display buffer is repeatedly copied to your video system.When this is complete (anywhere from 100 milliseconds or less, to hours for Hollywood quality computer animations), OpenGL redirects the computer's video system to display the new display buffer. The old is dealt with by your program. By default, it is erased and everything is redrawn from scratch. You can alter that for special effects or learning experiments when you know more OpenGL. A \textit{callback function} in an OpenGL program is a modification of a default function inside the so-called \textit{display loop}. Once the display loop is started by the OpenGL command \texttt{glutMainLoop()}, the only way you can interact with it in real time is through the callbacks. For C-adepts, callbacks work with function pointers. In object oriented programming (OOP), overloading classes serves a similar purpose. \subsection{Notational convention} Recall that we use a simple meta syntax. $P_{foo}$ refers to an OpenGL matrix (or product of matrices), qualified by the subscript "foo". Similarly $D_{bar}$ refers to phrase in the pipeline to be elaborated later. Square brackets designate a push/pop pair for the current matrix on the stack. Braces are comments not otherwise part of the meta syntax. Recall that the \texttt{ glPushMatrix()} duplicates the top entry in the stack so that it can be modified and applied to a drawing without altering the previous top matrix. The \texttt{ glPopMatrix()} removes the top matrix on the stack and restores the prior one to the top. The brackets must be \textit{ properly nested}, i.e. while there can be brackets within brackets, they must not overlap. This way, the brackets can also be interpreted as branches in a tree graph. Finally, $X_{obj}$ is a function that actually draws vertices of an object on the screen. It represents a leaf in the tree graph. A pipeline graph is \textit{well formed} if the push-pop brackets are properly nested. It is \textit{complete}, if it begins with an $\Pi}, and every drawing path ends with an $X$. \section{Deconstruction of drawcons()} For a first pass, assume the binocular flag, \texttt{ binoc == 0 } i.e. "is false", so that the second eye's viewport is not drawn. The double == is part of C-syntax and means "really equal", not just setting the LHS to the RHS, as in \texttt{binoc=0}. OpenGL has many stacks. We are concerned only with the \textit{Projection stack} and the \textit{Modelview stack}. On the projection stack we load the identity $I$ and multiply by the OpenGL matrix $\Pi_{frust}$ corresponding to a frustum of a rectangular viewing window (use of "window" predates Microsoft and belongs to Silicon Graphics!). The exact meaning of the parameters are discussed elsewhere. Immediately afterwards we draw the stars with \texttt{drawstar() } to get the initial phrase in the pipeline: \[ \Pi_{frust} D_{stars} .\] On the Modelview stack, we load the identity to avoid using junk already theres, then multiply by one of four maintained matrices by flag \texttt{whichmat}: \[ P_{vu}= P_{cam} \mbox{ or } P_{aff} \mbox{ or } P_{tor}^{-1}\mbox{ or } P_{tea}^{-1} \] depending on your choice. In the original illiSkel, there are no choices. Only $P_{aff}$ is maintained. In the customary C/C++ OpenGL codes, this matrix is hidden from the applications programmer. We draw all that is to be drawn. So we can see what we're doing, there is a final drawing of the \textit{messages} on the heads-up display. The heads-up needs its own viewport and projection matrix, see below. We have the entire pipeline without the details: \[ \Pi_{frust} D_{stars} P_{vu} D_{all} D_{msg} .\] We next unpack this summary of the OpenGL geometry pipeline for this RTICA. \subsection{Deconstruction of Component Functions} What we do next should remind you of unraveling symbols in your calculus course, as in integration by substitution, or the chain rule. \subsubsection{The Stars} \[ D_{stars}=[P_{stars}X_{dots}]. \] The stars in the basic version of this code are just random dots placed very far away. They do not move. When the observer's head is turned, then the stars are turned by a rotation matrix. Note that the observer's translation (forward, sideways, up/down motion) has no effect on the stars. \subsubsection{All the Drawings } \[ D_{all}=[P_{tor}D_{tor}][P_{tea}D_{pot}]. \] Since we want to place the torus and the teapot independently into the same coordinate system, we use a pair of push/pops. If, for example, you want a second torus to link the first and stay linked, the first bracket would become $ [P_{tor}D_{tor}P_{tor2} D_{tor}] $, where the second torus is rotated by 90 degrees and translated to one side, before it is drawn again. Try it! \subsubsection{Martin Newell's Utah Teapot} \[ D_{pot}=[X_{utah}]. \] It is worth your while to google the Wikipedia article on the teapot. It comes with the GLUT distribution as a pre-computed object for your convenient. By all means, make the second object in illiTorpot something less of a cliche, as an exercise. Also note, the push/pop in this instance is unnecessary, because no placement of the teapot is made. But you can certainly place the teapot somewhere else. So the brackets are already in the code. Just construct your own place matrix. \subsubsection{The Torus} \[ D_{torus}=X_{torus}. \] This is the placeholder for the primary object you want to draw. This part of the code is discussed elsewhere. \subsection{The Messages} \[ D_{msg}=\{newViewport\}[\Pi_{ortho} [X_{labels}]]. \] This drawing function calls for a new viewport. Since this is an unusual and thing, we don't invent special symbols. And it isn't part of the geometry pipeline, though it effects what you see on the screen. All are by now familiar with what is just called a \textit{window}. You open windows with a mouse, resize them, move them around, closed them. There is even a Microsoft product called ``Windows", although Microsoft didn't invent windowing. Neither did Apple. It was invented at Xerox-Parc, and used a 3-button mouse. Apple, initially decided that business men (for whom the MacIntosh was intended) are too clumsy to handle a three button mouse, and provided only a 1-button mouse. Microsoft offered a 2-button mouse to one-up Apple. But for the past decade, everybody saw the wisdom of Xerox and uses pretty much the same windowing system and 3 button mouse. The OpenGL viewport is almost always synonymous with a window. But Silicon Graphics (which invented OpenGL) knew better. What if you want to display a mosaic of many views inside the same window e.g. a stereopair, or an overlay on the entire screen, as with our \texttt{messages()} That's when you specifiy new viewports. \subsection{Full Elaboration} We now put it all together, the original \[ \Pi_{frust} D_{stars} P_{vu} D_{all} D_{msg} \] becomes \[ \{viewpt\}\Pi_{frust}[P_{stars}X_{dots}] P_{vu}[P_{tor}X_{tor}][P_{tea}[X_{utah}]}]\{newvp\}[\Pi_{ortho}[X_{msgs}]] \] \section{Navigation with Chaptrack} So far we only know the structure of the pipeline, not how to the user input with mouse and keypad is effected on the various placement matrices in the pipeline. Here we discuss only the most difficult one, the native navigator for illiStuff. In the illiSkel chaptrack is called in the \textit{ idle function}. This callback is, on a multiprocessor computer, done by a separate CPU so as not to impede the graphics engines with computation. There, the input devices are polled asynchronously from the graphics cards, and the processed information ( the changed entries in the various placement matrices) is made available to the pipeline when it is requires. On a single processor machine, the idle() function is done between successive display calls. Thus, heavy duty calculations can slow down the animation, though not in this case. \subsection{Chaptrack} This subroutine borrows the graphics pipeline to do some matrix arithmetic. This is generally a bad idea, but here it is meant to teach you the pipeline features. If you do not want to write your own matrix arithmetic functions you can also use the third stack ordinarily used for textures. Because we push the Modelview stack, nothing else is affected by this occasional loan of the stack here. Note that the input for the function \texttt{chaptrack(MAT, PAW, XX, YY, SHFT)}, where MAT=$P_M$ is an arbitrary matrix. We can expand $P_M=[U,m]$, but the brackets do not mean push/pop. They remind you that the components of an OpenGL matrix is a Euclidean rotation $U$ followed by a translation by $m$. So $U$ is a 3x3 orthogonal matrix and $m$ a 3-vector. Reading by columns, remember an OpenGL matrix a flat array of 16 floats, $m == (P[12],P[13],P[14])$. \textbf{ More later, or elsewhere, on OpenGL matrices.} The remaining three inputs contain the mouse button situation, the x-y coordinates of the mouse when polled, and the state of the shift-key respectively. Chaptrack continues by computing $P_{aff}^{-1}$ if \texttt{ chapmat==3}. It next calculates a small displacement \texttt{ dx, dy} of the mouse from the center of the viewing window, but with "dead zone". Then it modifies various placement matrices from the left by constructing a matrix on a clean stack by multiplying the identity matrix on the right. We use short lines to leave room for comments. But this is one long push/pop. [$I$ if3{$P_{aff}^{-1}$} \\ ifturn{$T_m$} \\ $R_{dx}R_{dy}$ ifshift{faster} \\ iffly{[$P_{stars} \rightarrow P_{stars}$]} \\ ifarrows{other translations} \\ ifturn{$T_{-m}$} \\ $P_{aff} P_M \rightarrow P_M $] \\ and finishes up by rotating the sun (light source) in the opposite direction to keep the bright spot from the same direction on a turning object. \subsection{Explanation} The best way to understand this lesson is to choose various of the the (greater than $2^6$) cases and write down what Geometry Pipeline matrix product (as in the Full Elaboration above) actually looks like in each case you considered. \textbf{ Examples follow. I would appreciate it if you looked for and reported typos, especially misinterpretations of the code into symbols.} \end{document}