Before they can be stored, shaders have to be read from files, sent to the GPU, compiled, and linked. Only then can they be used, and there is no point storing a broken shader. Textures also need to be read and loaded. As well as models. Configuration options can also be read from files.
All these parts of the program doing similar actions indicate a need to separate the file reading from the parsing. How to do it, though, and what to pass along?
Factoring the 'ReadFile' code away
Let's start with the shaders. The source for each kind of shader is found in .vp and .fp (and .gp) files. These stand for Vertex, Fragment and Geometry programs. They are regular text files, nothing fancy. The way I'm doing it right now is by calling a function in the GLTools library, which takes the name of the files and loads the shaders. It works, but it violates certain rules I want to enforce, such as the resource layer knowing about resources, not about the file system.
Still, we can scavenge the function (the license on it is very permisive). Interestingly, the function already separates the code that reads the file from the code that loads the function, which is exactly what we want to do.
glShaderSource is the OpenGL API call we want to use to load the shader to the GPU. The way it wants us to send it the shaders is as a pointer to a null terminated string of GLchars, which is essentially an array ending in '\0'. This means that, in this case, we'd want the file reading part of the program to load the file into an array in memory, and send a pointer to the array to the ShaderManager, which would then be in charge of loading it in the GPU and storing the result of the operation.
This works for any text files, but textures are genrally stored as image files. These are genrally binary encoded and compressed. So how to handle them? One alternative is to read the file, and pass the content in the state that it was received to the resource layer, and let the Resource Layer handle compression and the like. Another is to let the Application Layer deal with the matter of filetypes and enconding and just hand the resulting data to the Resource Layer.
Decoding in the Resource Layer vs. decoding in the Application Layer
I feel more comfortable with this second approach, myself. For one, it is not necessarily true that only the Texture Manager will want to decode image files. Heightmaps, for example, are one way of storing terrain topography. If I ended up using a picture format for terrain and the same format for a texture, I'd have to repeat the decoding process in each manager. This is what I want to avoid.
As a result, I'll keep functions to decode each filetype in the application layer, and will simply pass along pointers to arrays of bytes to the rest of the program when required. These can then be interpreted as appropriate by each part of the program.
Passing along the full file can be problematic however. Specially in binary files, which can contain structures. There would be little point in decoding unless we were to turn the file into something usable. Now, in the case of shadrs, just passing along a null terminated string is fine, that is what we want. But in the case of images, for example, we could pass along a more elaborate structure, including the height and width of the image, color depth, etc. This is the information the Resource Layer wants.
We can repeat the structure we came up with before for handing resources to the Engine Layer in the communication between the Application and Resource Layers. For starters, we would need a base class for the different structures we might need to pass along. We could re-use 'Resource'. Then we'd need types for the different sort of information that is being entered into the system. "Text" for char arrays (there's a multitude of string classes that could be used), "Image" for textures or heightmaps, and the name of whatever appropriate structure is being sent for other, more complex files.
We can think of these as 'un-processed' resources, which are processed by the Resource Layer into the game resources our engines can use.
As before, we don't want each of our managers dealing with the functions in the Application Layer personally. Instead, when a request for a certain resource is sent their way, they should check if the resource is already stored and return it or, if not, forward the request to load it from file, store it, and return it to whoever asked for it. This forwarding should be done through an interface into the Application Layer, the same way the Engine Layer connects to the Resource Layer.
Interfaces connecting the different layers
This takes care of the communication aspect, but we still need to determine how to model the application layer, which for now would consist simply of a smattering of functions. Such as loading files and converting them to and from different file types.
No comments:
Post a Comment