Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

You are making a 3d engine. You want best of multiplatform worlds. Suddenly you realize that if you want to use Direct3D on Windows machines and OpenGL on OSX/Linux, you'll have to sacrifice supported features of both to least common denominator.

Some may use OpenGL across three OS', since it appears to be least common denominator by itself. All is good. Then, you have to port your graphics API backend to Nintendo's GX, you also have to make a PS3 and Xbox360 path.

What do you do? Do you design your own API which is least common denominator in itself and write backend implementations for it for each platform or do you write for each platform it's own branch?

If you opt to design your own API, do you use bridge pattern or your own voodoo? Where does madness stop where you realize everything and kitchen sink approach must stop and you basically have separate engine for each platform as a branch. Or you stick to everything and kitchen sink and keep platform specifics in backend module specializations for each platform.

share|improve this question

5 Answers 5

up vote 8 down vote accepted

I'm not a fan of the least-common denominator approach. If you do that, you may end up with crippled features and poor performance.

Instead, what I've done in the past, is to provide slightly higher level functionality in a library. That library is (mostly) API agnostic and can be used anywhere, but the implementation of the library is completely different for different platforms/graphics backends. So for example, instead of having a SetStateX() function, you have higher functions like RenderMesh(), or CreateRenderTarget().

It will be more work than a really thin layer whenever you move to a new platform, but it will be completely worth it because you'll be able to implement things in the optimal way for that platform, and you'll be able to take advantage of native, unique features.

One more thing: Don't be afraid of slightly breaking the encapsulation. There's nothing more frustrating that knowing you're in a platform with certain capabilities and not being able to use them. Leaving a backdoor of some kind so higher-level code can take advantage of the platform is very useful (for example, being able to retrieve the D3D device or OpenGL context).

share|improve this answer
2  
I think you said what I was trying to say, only better. –  AShelly Jul 23 '10 at 0:04

All I can say is take a look at Ogre3D. It's written in C++, Open source (MIT License now) and runs on every major platform out of the box. It abstracts out the rendering api and can switch from using DirectX to OpenGL with just a couple of settings. However, I do not know enough about the differences between feature sets of DirectX and OpenGL to say that it does or does not support a specific feature.

Torchlight by Runic Games was written using Ogre and I've played that on Mac and PC and it runs very well on both.

share|improve this answer
2  
+1 for Ogre approach. I know about it, have read through some of the code. I was more interested to hear personal stories about the approach though and what other people do in situation like that. –  Keyframe Jul 14 '10 at 23:53
2  
Thanks! Well I would do it mostly the way Ogre did. I've used the Interface/Factory approach in a lot of cross platform development and I'm actually not sure how I would do it otherwise. I would say that you absolutely need to be working on multiple platforms at the same time though. Do not try and code everything on Windows and than try to port to Mac for instance. –  Casey Jul 15 '10 at 0:12
    
Yes! I was growing really tired of rolling my own cross-API wrapper, and then I just started using Ogre. Haven't looked back. :) –  jacmoe Aug 4 '10 at 0:31

I haven't done this for graphics, but I did create a cross-platform audio toolkit (PC/XBOX/PS2). We went the route of creating our own API with a least-common-denominator capability as well as optional platform-specific capabilities. Here are some lessons learned:

The key is to define a processing path that encapsulates the core capabilities of each platform, and allows growth. In order to do this, you need to really understand each platform's low level API so that you can identify the right abstractions. Make sure the chain works for the least capable platform, while providing access to the most capable patform's advanced features. Do the work to get this right and you will save a lot of effort later.

For audio, the chain was something like SoundSources -> [Decoders] -> Buffers -> [3D Positioner] ->[Effects] -> Players.

For graphics, it may be Model -> Shapes -> Positioner -> Texturer -> [Lighting] -> [Particle Effects] -> Renderer. (This is probably a completely wrong set, I'm not a graphics guy).

Write a front-end API which handles your core objects, and a platform-specific back end which maps the API to the low-level capabilities. Provide a best-effort for each capability. For instance, on the PC and XBOX the 3D audio positioning was done using the HRTF capabilities of the sound chips, while PS2 used a simple pan and fade. A graphics engine might do something similar with lighting.

Implement as much of the front end as possible with platform neutral code. The code to attatch a reverb object to a sound object, or a texture asset to a shape object should be completely common, as should the code to iterate through and process active objects. On the other hand, the low level objects can be completely platform specific except for the common interface.

Make sure the API, or configuration files, allow the user to specify platform-specific options. We tried to avoid pushing platform-specific code to the game level by keeping it in configuration files: One platform's config file can specify "effect: SuperDuperParticleGenerator" while another says "effect: SoftGlow"

Definitely develop the platforms in parallel. Make sure that the platform specific interfaces are well defined and testable on their own. This prevents a lot of the "is it the platform level or the API level?" issues when debugging.

share|improve this answer

I'm writing an open source game engine called YoghurtGum for mobile platforms (Windows Mobile, Android). This was one of my big big problems. First I solved it like this:

class RenderMethod
{

public:

  virtual bool Init();
  virtual bool Tick();
  virtual bool Render();

  virtual void* GetSomeData(); 

}

Did you spot the void*? That's because RenderMethodDirectDraw returns a DirectDraw surface while RenderMethodDirect3D returns a vertex pool. Everything else was split as well. I had a Sprite class that had either a SpriteDirectDraw pointer or a SpriteDirect3D pointer. It kind of sucked.

So lately, I've been rewriting things a lot. What I have now is a RenderMethodDirectDraw.dll and a RenderMethodDirect3D.dll. You can, in fact, try to use Direct3D, fail, and use DirectDraw instead. That's because the API remains the same.

If you want to create a sprite, you don't do it directly but through a factory. The factory then calls the correct function in the DLL and converts it into a parent.

So, this is in the RenderMethod API:

virtual Sprite* CreateSprite(const char* a_Name) = 0;

And this is the definition in RenderMethodDirectDraw:

Sprite* RenderMethodDirectDraw::CreateSprite(const char* a_Name)
{
    bool found = false;
    uint32 i;
    for (i = 0; i < m_SpriteDataFilled; i++)
    {
        if (!strcmp(m_SpriteData[i].name, a_Name))
        {
            found = true;
            break;
        }
    }

    if (!found) 
    {
        ERROR_EXPLAIN("Could not find sprite named '%s'", a_Name);
        return NULL; 
    }

    if (m_SpriteList[m_SpriteTotal]) { delete m_SpriteList[m_SpriteTotal]; }
    m_SpriteList[m_SpriteTotal] = new SpriteDirectDraw();

    ((SpriteDirectDraw*)m_SpriteList[m_SpriteTotal])->SetData(&m_SpriteData[i]);

    return (m_SpriteList[m_SpriteTotal++]);
}

I hope this makes sense. :)

P.S. I would loved to have used STL for this, but there's no support on Android. :(

Basically:

  • Keep every render in its own context. Either a DLL, or a static library or just a bunch of headers. As long as you have a RenderMethodX, SpriteX and StuffX you'r golden.
  • Steal as much as you can from the Ogre source.

EDIT: Yes it does make sense to have virtual interfaces like this. If your first attempt fails, you can try another render method. This way you can keep all your code render method agnostic.

share|improve this answer
1  
Does it really make sense to have a virtual interface if you are never going to have more than one implementation active at the same time? –  NocturnDragon Jul 22 '10 at 20:07

I like to use SDL for this. It's got renderer backends for D3D, OpenGl, OpenGL ES, and a handful of other platform-specific backends, and it's available for all sorts of different platforms, and currently under active development, with bindings to many different languages available.

It abstracts away the different renderer concepts and makes creating video (as well as handling sound and input and a few other things) available in a simple, cross-platform API. And it was designed by Sam Lantinga, one of the lead devs at Blizzard, specifically for making porting games and creating cross-platform games easier, so you know you're dealing with a high-quality library.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.