Tuesday, April 14, 2015

Porting to Windows

If you've been following the development of Qi so far, you may have noticed that a few of the things the engine is doing are generally UNIX-only (__attribute__ ((aligned(16))) comes to mind). That's because the main development machine for Qi is a Mac and everything done up until this point has been in Xcode. However, in the interests of having more compliant (and generally better) code I've decided to make sure that the engine can compile with multiple compilers (and therefore multiple platforms). The first target is Windows. However, the port wasn't as straightforward as I had hoped....

Before I get started, note that I'm targeting Visual Studio 2013.

Cross-Platform Alignment


First and foremost, there are just some cases where you have to say, "this is Windows, I need to do it this way" or "this is Mac, I need to do it that way". The most straightforward way to do this is to use a special define from Visual Studio, _MSC_VER. In Qi, I've decided to create the define QI_WINDOWS to tell any code including the Defines.h file that the compiler is currently a windows compiler:

#if defined(_MSC_VER)
    #define QI_WINDOWS
#endif

Then, Qi can make decisions about what code to use based on the compiler being used to compile it. For example, everything in Qi's math classes must be aligned to 16 bytes in order to properly support SIMD operations. Previously, Qi achieved this by specifying the aligned attribute in front of the class name, e.g.

class __attribute__ ((aligned(16))) Vec4 {};

However, this is a UNIX-only way of aligning the structure. In C++, we do have access to the very nice, cross-platform function alignas() which takes as its parameter the specific alignment that you want. While this is very convenient and doesn't require any compiler-specific code, Visual Studio 2013 does not yet support it (though Xcode does via clang and GCC) making it not a viable option to use in this case. Therefore, we have to fallback to the older way of doing things; define a macro based on which compiler we're using and use that in the class definition:

#if defined(QI_WINDOWS)
    #define QI_ALIGN(x) __declspec(align(x))
#else
    #define QI_ALIGN(x) __attribute__ ((aligned(x)))
#endif

class QI_ALIGN(16) Vec4 {};

Note that in Windows, we're using the Visual Studio version for alignment (__declspec(align(16))) and on UNIX, the same alignment code that we used before. So far, this is the only place in the engine (as it currently stands) where Qi has to do anything that is compiler-specific. In every other case the engine is making use of cross-platform C++ libraries (mostly from C++11) which get around having to directly write code like this. However, it's expected that in the future there will be more situations like this however so having the definition of QI_WINDOWS already setup will make future code pretty trivial to split between compilers.

Unions and User-Defined Types


During development of the math classes, I expected to have to make the alignment changes sometime in the future so it was no big deal. However, I learned that the way the math libraries were artchitected to make use of the already-coded Vec4 object simply would not work. Why? Turns out the C++ standard specifically states that you cannot have object inside of a union that have user-defined constructors or assignment operators (link). Basically, anything that is not a POD type cannot go into a union. Well, since I was making use of the Vec4 type to get easy SIMD code for my matrix and quaternion classes, this just wan't going to work. For reference, here's how the Matrix4 object used to be defined:

union
{
   float m[16];
   Vec4 m_rows[4];
};

Both clang and GCC had no problem with this. Conceptually it makes sense because underneath a Vec4 is merely 4 floats and the way the class is specified will make it take up the same amount of space as a float array of size 16. However, Visual Studio is much more restrictive in this case and makes sure to enforce the spec, therefore this code will not compile. This forced me to rethink the way I designed this and what I settled on I actually like better anyways. Instead of containing all of the SSE code inside of Vec4, Qi now defines a special file that wraps all SSE variables and function calls (SSEUtils.h). This way, the engine can evolve to use future SIMD libraries without having to make any drastic changes to the math classes themselves. This new file defines a new variable SSEType which is used by the math classes to define an SSE variable which can be used in SIMD operations (it's really just a typedef for the SSE-specific __m128). It also specifies the proper structure alignment needed to work with this type. Finally, all of the SSE-specific code has been taken out of the Vec4 class and placed into floating functions which take in an SSEType and return the appropriate variable based on the function. Necessary functions include:
  • Dot product
  • Zeroing
  • Addition
  • Subtraction
  • Multiplication (both scalar and vector)
  • Division (both scalar and vector)
Using just these few functions, we can do just about any math operation that the math library will do using SIMD instructions. Now, Matrix4 is defined as:

union
{
   float m[16];
   SSEType m_rows[4];
};

The same is true of Vec4 and Quaternion. You can check out the new math library here.

Line Endings


Finally, when you hit the 'enter' key on Windows, it places a carriage return and a newline at the end of the line. UNIX systems however, just put a newline at the end. Therefore, files edited on both systems will have lots of spacing/newline issues and won't look correct. To fix this, you can easily tell git to handle line endings for you. There is a pretty simple process outlined here. Qi makes use of this in order to normalize all line endings to just use the newline character.

----------

Well that's it. Fixing these three problems were the only things preventing an easy port to Windows. The Visual Studio solution along with all necessary libraries have been checked into the git repository and are ready for use with any instance of Visual Studio 2013. Until next time!


No comments:

Post a Comment