• 9
• 11
• 9
• 20
• 12
• entries
437
1000
• views
336487

Platform compatability

129 views

I want to talk about platform compatability as it plays a fairly important part in my coding life. I use cross-platform libraries and try to write code that's as portable as possible. This is why I choose OpenGL over DirectX, PhysFS over the Win32 file API and SDL/GLFW over writing window/input code myself.

I want to answer this here:-

Quote:
 Original post by RavuyaI try to make code as portable as I possibly can, as a rule of thumb. Usually all I have to do to port between Linux, OS X and Windows is change a few lines of header directives the first time I build, and then set up an #ifdef for successive builds. Easy!

That's how I do it too - nice and easy! If you've never written for another platform but feel like you would like to, try some of this stuff - it can't hurt [grin].

My system is set up so that any specific graphics or platform files are implemented in their own .cpp, with subtle differences being tied up in the #ifdef blocks you suggested. Compiling for another platform/API is as 'simple' as setting a new define and running the platform's Makefile to compile the relevant source.

Right now, my directory looks something like:

\src  sys_system.cpp  sys_system_SDL.cpp  gfx_graphics.cpp  gfx_graphics_SDL.cpp  gfx_graphics_OGL.cpp  in_input.cpp  in_input_SDL.cpp  .. etc ..\include  sys_system.h  gfx_graphics.h  gfx_graphics_SDL.h  in_input.h  .. etc ..

I'm thinking about splitting out the various platform specific stuff into platform subdirectories to ensure it's not as cluttered, but you should be able to tell how things are separated out. All of the public interface functions are declared in the main, non-platform specific header (eg: gfx_graphics.h) and implemented in the platform specific .cpp file. For GMGX I am coding in a C-like style, so it's fairly easy to write code in this manner. If you were using object-oriented C++, you'd probably define your interface as an interface class and hook up a static 'Create' function to ensure the correct platform's implementation is used.

To port GMGX from Windows to the GP2X I went through and added a new system (sys_system_SDL.cpp), a new input system (in_input_SDL.cpp) and a new graphics system (gfx_graphics_SDL.cpp and tex_texture_SDL.cpp). Everything else in the code is forced to go through these abstract functions, so it's impossible to try and render a sprite 'manually' or update texture data by manipulating the data. Any data that comes from this platform/API specific code is either held in a common format (eg: my collision mask data derived from the texture bitmap) or is inaccessible outside of the system itself; this is achieved by providing handles to the data instead of pointers. A handle is specific to the system it's allocated from, so in one implementation it could be a masked pointer and in another it could be an offset into an internal array of real pointers. The idea is that it masks data from the user and forces them to go through the API - this is, in my opinion, A Good Thing (TM).

I like this way of coding, even if you're not planning on porting to another platform. For one, it helps encapsulate everything properly so that nothing should (in theory) get altered outside of your API implementation. Secondly, it allows you to rip out and change the whole implementation of subsystems very easily. If you wanted to write a DirectX renderer, for example, you could do so fairly easily (hopefully). An example of this is the filesystem abstraction I'm using. Originally I was using the C file API wrapped in a common interface (fs_* functions). I was able to tear this out and implement a filesystem based on PHYSFS in very little time just by coding a new implementation. If I ever needed to swap back to a C-file system I could just rebuild my source with the old file instead of PHYSFS.

Does anyone have any comments on this way of coding? I'd be interested in hearing your own experiences of developing in this manner...

Quote:
 Does anyone have any comments on this way of coding?

It sounds cleaner/tidier than my methods, I can comment on that much.

The only thing that I really don't like about similar architectures is when they have lots of #ifdef/#endif blocks to mark out the platform specific stuff. Something like:

#ifdef WIN32
#include <Graphics_D3D.h>
#elif
#include <Graphics_OpenGL.h>
#endif

is fine, but i've seen some examples where it's gone a bit crazy. Hard to read code makes for easy to introduce errors.

I'm also a little weary of the recompile for each platform part. I'm not sure theres an elegant way around it (each OS has a different binary format).

I'm a big fan of the modular architectures that use dynamic binding - you just need to drop in a new "something.dll" or "something.so" and it'll extract the interface it wants and no NOTHING else about it. But at some point you have to have some sort of kernel/"loader" in the native binary format..

In fact, I'd love to try that... where you have a simple exe with your main() and a published LoadModule(name,fptr) type functionality. That way you could just do a:

typedef void (*game_core_fn)(int*,char*) GAME_CORE_FN;
int main()
{
GAME_CORE_FN start;
(*start)( 12, "Hello, World" );
}

#ifdef WIN32

void LoadModule( char* name, GAME_CORE_FN fptr )
{
// Create the function ptr using Win32 dll functions
}

#elif

void LoadModule( char* name, GAME_CORE_FN fptr )
{
// Create the fptr using unix/linux functions
}

#endif



And if LoadModule() were accessible from the other libraries then they could, in turn, load any dependencies that they have.

I'll be quiet now. I'm rambling.
Jack

A very interesting post.

I've been working to port my own framework/engine to the Nintendo DS at my work during lunch hours. It previously worked on Windows, and a long time ago also on Linux (haven't check recently). So I've got quite some experience with this I feel.

I have the common .h file, but also a .cpp file with common stuff, and the following:
#ifdef NDS
#    include "nds/window.cpp"
#elif WIN32
#    include "win/window.cpp"
#endif


I structure my project with a lot of folders, more than I've ever seen anybody else use. So the os dependent part is under /os in my source tree, and it has a /nds and a /win beneath it. All platform specific files are in there.

Theoretically, this setup would allow me to compile for each and every platform with the same makefile (assuming it builds the to-build list from directory contents), but only changing a single, or possibly two defines.

This has worked quite well for me. I've been able to port what I had on the pc to the DS in less than 20 hours. Beyond that, I still needed to fix the renderer for the DS, but it was compiling and running fine.

jollyjeffers: How would that work? I've always assumed that the .dll/.so would have to be recompiled per platform anyway, so you could as well recompile the program itself.

Quote:
 jollyjeffers: How would that work? I've always assumed that the .dll/.so would have to be recompiled per platform anyway, so you could as well recompile the program itself.
I hadn't really thought it through fully so it probably wouldn't work [headshake]

I'm sure it made sense when I was typing, but on retrospect it doesn't seem to make much sense. I'll go give it some thought while I cook my dinner...

Jack