Besides studying for final exams and getting stabbed, which seems a bit of a self-fulfilling prophesy at this time, I’ve been getting some work done on Raxo, my software modeler and rasterizing renderer.
Raxo is, as described, a way for me to learn about all the compsci stuff I’m interested in: graphics, low-level assembly generation and optimization, and design.
Raxo uses AsmJit for Raxo’s equivalent of vertex shaders, rasterizing loops, the image buffer postprocessing/filtering, and maybe more. AsmJit generates actual executable functions from calls to its API at runtime, then allows them to be called from your C/C++ code. Why is this useful? It allows conditional branching (if, while, and for statements) to be eliminated from inner loops—the parts of the software that are executed millions of times every second, and that need to be as fast as possible—whenever they can be determined ahead of time.
For example, if you have a set of say, 8 lights in a scene to be rendered, but not all of them are on. A non-JIT compiled renderer would need to check through each light to see if the programmer has turned on the light, every time the lighting equation needs to be calculated. That could be once per vertex or even once every pixel! Raxo, however, can recompile its rendering function so that it generates lighting equation code only for the lights that are on, eliminating wasted CPU cycles evaluating those ifs and mispredicting branches.
This of course means that state changes are expensive. But then state changes are expensive with hardware too—much more expensive.
Having AsmJit prevents me from having to write a lot of parts of the library in C++ template metaprogramming. Using templates would allow conditionals to be evaluated and optimized out as the code is compiled. But then Raxo would be extremely difficult to understand and use, not to mention state changes would be impossible. How would you like to use a renderer that you have to make a new instance of from another class, just to turn on another light in the scene?
So, I avoided hardcore C++ templates, even if it meant having to generate assembly code. After all, I can abstract assembly generation through my own API, but it’s very hard to hide an ugly template API. See Anti-Grain Geometry if you want to see an example, as the API for it entails using nested templates parameters to generate an entire 2D graphics pipeline (filters, transforms, geometry transformation, etc.). Though I must say, AGG is blazing fast, accurate, and extremely high-quality. It is definitely a library that inspired me to write Raxo.
Raxo is also currently using PixelToaster as a floating-point color framebuffer (get this: Raxo—and my final project for the Stuyvesant course—renders to single precision floating point pixel buffers, for precision and dynamic range beyond those displayable with a computer screen). In addition, a separate file format, SPRx (Single Precision Raxo err… something) was developed based on Industrial Light & Magic’s OpenEXR HDR image format. In comparison, SPRx is significantly simplified because EXR was so much more than I needed.