In out last post, we talked about the shift we are currently making in Vortex, moving towards reference-counted resource management.
In this context, one of the problems we mentioned was the fact that due to the fact that C++ does not provide out-of-the-box type covariance/contravariance like C#, we ended up with some rather uncomfortable constructs for dealing with polymorphic data types.
Now, one of the great features of C++ is that the language is super-extensible. You can literally change the way many things work in C++ and, it turns out that after some research and experimentation (and a little C++ magic too), we found a way to implement C# covariance/contravariance for our custom smart pointers!
The latest improvements enable the following features:
- Implicit casts from a smart pointer (to an instance) of a derived class to a smart pointer of a base class.
- Static casts from a smart pointer (to an instance) of a base class to a smart pointer of a derived class.
- Compile-time errors from attempting invalid casts.
The following code fragments illustrate these points. These are taken directly from out test suite, with minor adaptations to simplify readability.
Fragment #1: Implicit cast from derived instances to base class instances:
ref_ptr<TransformNode> xformNode(new TransformNode());
ref_ptr<Node> node = xformNode; // Valid: implicit cast
Fragment #2: Static cast from a base class instance to a derived class instance. Note we called our static cast operator “ptr_cast”.
ref_ptr<Node> node(new TransformNode()); // Storing as a "more general" pointer
ref_ptr<TransformNode> pxformNode = ptr_cast<TransformNode>(node); // Valid: explicit cast.
Fragment #3: Finally, the following code is invalid, and will trigger a compile-time error:
ref_ptr<SimpleSceneGraph> sg(new SimpleSceneGraph());
ref_ptr<Node> node = ptr_cast<Node>(sg); // Illegal conversion: types are not related
We are very happy with the result and are looking forward to continue migrating the Vortex codebase to our new smart pointers. Programming with Vortex just got a whole lot easier and safer.
Now that we have advanced support for a dual rendering pipeline in Vortex, we are now shifting our attention into a completely different topic. This time we are looking into overhauling the engine’s inner resource management.
To this purpose, we are conducting experiments shifting resource and scene management from raw pointers to smart pointers. The idea of using smart pointers is to enable each resource to be automatically deleted once it is no longer referenced by any component or object. This is not to be confused with Garbage Collection. The main difference consists in that smart pointers allow us to simplify memory management without sacrificing deterministic object destruction and without incurring into the memory and CPU overhead of having the Garbage Collector kicking in to rearrange the application’s Heap at any time.
So far, this has proven a very interesting experience. We’ve bumped with some unexpected issues along the way, but we’ve also run into a couple of nice surprises. Among the problems encountered, there’s the fact that since C++ does not support out-of-the-box covariance/contravariance (in the sense C# does), exposing the polymorphic behavior of the scene objects became a little more involved that what it used to be when using raw pointers.
On the bright side, using smart pointers dramatically improves some of the roughest edges of the API. Just to pick a simple example, let’s consider image lifecycle management. Without smart pointers, Texture creation from images in Vortex might look like this:
Vortex::Image* pimg = Vortex::Image::loadFromFile("./resources/texture.png");
// Create Texture from image data...
// loadImageFromFile transfers pointer ownership,
// we must therefore not forget to delete the image:
Using Vortex’s smart pointers, however, we can forget about the explicit delete operation:
ref_ptr<Vortex::Image> pimg = Vortex::Image::loadFromFile("./resources/texture.png");
// Create Texture from image data...
// Image object ownership is now managed by the
// smart pointer. The pointer releases the data
// upon exiting this scope (unless the pointer is copied)
ref_ptr<T> is the typename we’ve given to our in-house smart pointer implementation. These allow us to easily cast inside type hierarchies as well as dropping into the raw pointer when needed, allowing us to dodge the performance tax in performance-critical code.
So far the results seem promising. We will continue experimenting moving the rest of the codebase to smart pointers and hopefully we will be able to include this along with the dual rendering pipeline in the very next version of Vortex: Vortex 2.0.
Continuing with the news related to the Vortex Engine, I’m glad to announce that the next version of Vortex will come with two separate rendering pipelines: a fixed rendering pipeline and a programmable rendering pipeline that supports custom shader programs.
Comparison of the Rendering Pipelines available in Vortex Engine. The image on the left represents the Fixed Pipeline. The image on the right represents the Programmable Pipeline (click to enlarge).
Supporting two pipelines enables the developer to use the most appropriate rendering pathway for the target device’s capabilities.
Newer video cards for Desktop computers and the latest mobile devices can be programmed for though the programmable pipeline, whereas older devices (such as the original iPhone and the iPhone 3G) can still be supported through the fixed pipeline.
Anyone in the field of Graphics will be able to tell that the fixed pipeline will not be able to provide the exact same visual capabilities as the programmable pipeline, however, it should be less resource intensive and thus more suitable for some older devices.
The programmable pipeline, on the other hand, will allow Vortex applications to leverage the hottest techniques in Computer Graphics such as Bump Mapping, Specular Mapping, Blur and Depth of Field (among others), dramatically improving the visual detail.
This list provides, at a glance, the criteria for selecting the appropriate Vortex rendering pipeline depending on the target device:
- OpenGL 1.1 / OpenGL ES 1.1 devices -> Use Vortex’s Fixed Pipeline
- OpenGL >= 2.0 / OpenGL ES 2.0 devices -> Choose between Vortex’s Fixed Pipeline and Vortex’s Programmable Pipeline (recommended).
Needless to say, we’re very excited about the possibilities that this brings to the table and are very eager to see to what extents can the Vortex Engine be now pushed.
In the picture above you can see a sample comparison between the lightning model of the fixed pipeline (hardware-accelerated Gouraud Shading) and the one that the programmable pipeline offers (hardware-accelerated Phong Shading with specular reflections in this case).
This is just the tip of the iceberg. Stay tuned for more updates on the Vortex Engine!
Someone once said there is no building the engine without building a game. There is nothing like actually using your own tools in order to pinpoint the weak spots of the Engine.
Perilith Knight with the "knight skin".
Using Vortex to build different 3D applications, I’ve noticed that it may, at times, be a bit more verbose than one would expect from an Object Oriented C++ API.
One of the roughest edges in Vortex was Md2 model animation support. Until last week, integrating animated models into a Scene Graph was unnecessarily complicated. Fortunately, this is no longer the case. Adding an animated Md2 model to a Scene Graph is now as easy as adding any other node type.
Controlling animations and skins of Md2 Models programatically is now a breeze in Vortex.
Here are some captures from the famous Perilith Knight model. I may upload a YouTube video showing the smooth animation at some point.
Perilith Knight using the "evil skin".
Perilith Knight using the "ctf_r skin".
Stay tuned for more updates on the Vortex Engine!