![]() ![]() ![]() ![]() ![]() ![]() Introduction to ShaderLib Project Goals: ShaderLib's missions is to create a single tool kit that provides the services needed by anyone developing a tool or graphics engine the needs to understand shaders as implemented in Quake 3 Arena. ShaderLib's mission is NOT to provide a rendering engine to actually draw shaders, nor to provide anything but the most basic tools for manipulating them. It is up to the developers using the library to create these. It is hoped that ShaderLib will be so compelling that no one would need to implement their own shader parsing and compilation routines. This may not be possible, some people will have very special needs that will require too much work to add to ShaderLib and others will want the experience of implementing the same functionality themselves. Ultimately, the author's goal for ShaderLib is to grow beyond its roots in Quake 3. The shader system originally designed by John Carmack is brilliant and the concept should be developed further. The philosophy of the design of ShaderLib is to appear shallow but at the same time possess depth. The result is that the interface is simple enough for novices to use, but allows for a variety of customization which makes it suitable for many applications. Basic Design: The core of ShaderLib is the parser. This is because most of the other functions in the library are meant to be used by the parser or are used to setup how the parse function works. After the parser is invoked, it will also call some user defined callback functions to perform tasks that need to remain flexible. The library is simplified by providing a sensible default callback for everything, thus allowing a useful program that uses the ShaderLib to consist of a single call to the parser and nothing else (as simple as "Hello World!"). Here is a brief description of each callback: input - like getc, used to read shader data output - like printf, used to save shader data message - like printf, used to report errors approve - true or false, determines if a shader should be parsed compile - final step, called after shader is parsed The Parsing Process: The process of parsing starts by building up tokens (while skipping comments), and then intrepreting what they mean. The input function allows the tokens to be built up one character at a time. The first token is assumed to be the a shader's name. This name is passed to the approve function. If it is approved then parsing continues, else the parser skips on to the next one. While parsing, an error or warning may be found and messages are sent to the user. When the parser reaches the end of the shader then a compile function is called to allow the user to access the shader data. The compile function may use the output function to save or report any information about the shader. This process is repeated until the shader input stream is exhausted. Long Description of User Callbacks: input The input function reads the shader stream one character at a time. The default function reads from a file (stdin by default), and an alternate function provided with the library reads from a string. outputThe output function is not used by the parser, but it is available to be used by provided compile functions (report and save, see below after the explanation of the compile callback). The default output function provided outputs to a file (stdout by default), but an alternative version outputs to an area of memory. The function is meant mainly for use by the provided compile functions, because the user is not expected to have to rewrite these functions if they want to change where report and save output their information. messageThe message function is used by the parser to complain. If something important happens, like an error, then it is reported through this function. It is like printf, but has the ability to add extra information like the line where the error occured and what command was being parsed. The default version outputs to a file (stderr by default), but there is also a version to outputs to a string. An engine could use it to output to the console, and a Win32 or X application could use it to print to an output window or log. approveThe approve function is called by the parser to check whether the shader it is about to parse should be parsed or skipped. The approve function is only given the name of the shader to determine this. This can be used by engines and tools so that only shaders that are going to be used by the models and/or the level are actually parsed. compileThe compile function is where the user finally gets their hands on the parsed shader data. Everything is nice and organized and all the defaults and special conditions are taken care of. The only thing left for the user to do is whatever is specifically needed by the application. Provided Compile Functions: Their are two compile functions provided with the library. The report function will dump a complete accounting of the shader using the output function, and the save function will write the shader using the output function. The report function is for debugging purposes. It is used to make sure that everything is being read in properly, and that all the default values and special cases are handled correctly. The save function is provided so that shader structures generated by a shader editor program can be saved out. Why is ShaderLib not written in C++? Some may ask this because if ShaderLib were C++, then this might be handled by the user sub-classing a ShaderParser object and overriding the functions they needed to. It is a classic design pattern used for parsers. I opted to implement this design pattern in C because it is less convoluted to wrap C in C++ than to wrap C++ in C and I feel both are needed. Also, the effect is the same even without the syntatic sugar of C++. A C++ wrapper for this library would be a neat thing and will probably be added in the future when the library has stablized. Next Section |