Zayats
Zayats

Reputation: 281

Multiple render systems

Some time in the near future, I will begin developing a game engine. One feature that I want to include is having multiple render systems such as directx 9/10/11 and OpenGL. This way, a game using this engine will be able to support more players since if one render system doesn't work, it will revert to a different one.

So my question, how would I go about doing this? I researched a bit and saw that Ogre3D uses something like this but I have no idea how they do it. I basically want to be able to plug in different render systems and use the same code to run it.

Upvotes: 4

Views: 5214

Answers (4)

Necrolis
Necrolis

Reputation: 26171

Use the power of C++ interfaces!

create an abstract that defines the interface to you rendering system

class Renderer
{
 //...
    virtual void DrawRect(Color c, Vector2 Pos) = 0;
 //...
}

then implement it in each render system:

class D3DRenderer : Renderer
{
    void DrawRect(Color c, Vector2 Pos);
}

this method is fast, simple, and efficient, which is why it is used in many game engines. It should be note that it does require one to be aware of certain API restrictions when building things like this (even small thing like row major vs column major matrices).

Upvotes: 5

josephthomas
josephthomas

Reputation: 3276

You could create an interface class that allows you to further extend for the API you are hoping to expand to.

I have done this in the past to create a DirectX9\11 Renderer and I am hoping to expand this to OpenGL soon. There is certainty a lot to the task, but it is easy to explain a basic working of it. Unfortunately the project I am on is closed source, so if you have any questions about this please don't hesitate to ask.

First you will want to create a separate project to be used as a .lib/.dll, I called this "RenderInterface". This will contain basic interfaces for VertexBuffer's, IndexBuffer's, Shaders, and most importantly, IRenderInterface and IRenderUtility, which in the later implementation could potentially hold items such as such as ID3D11DeviceContext and ID3D11Device.

My two most important items in the "RenderInterface" project, are IRenderInterface and IRenderUtility. The idea here is to have IRenderInterface do the heavily lifting, such as creating render targets, presenting, and initializing the API. Where as IRenderUtility will do more common things, such as creating vertex/index buffers, creating shaders and rendering. I pass IRenderUtility around more frequently on initialization and rendering, and IRenderInterface is rarely passed around. This is to prevent the users from doing accidental operations when they do not need to. This project will also include some common structs\enums that will be used throughout the code base.

I then create another project, such as D3D11RenderInterface. This D3D11RenderInterface will have the implementations of the IRenderInterface, IRenderUtlity, IVertexShader, IVertexBuffer, etc.

An overlysimple example would be

class IRenderUtility
{
public:
    virtual void Draw(unsigned int vertexCount, unsigned int vertexStartOffset) = 0;
};

class D3D11RenderUtility : public IRenderUtility
{
protected:
    ID3D11DeviceContext *mD3D11DeviceContext;
public:
    void Draw(unsigned int vertexCount, unsigned int vertexStartOffset)
    {
        mD3D11DeviceContext->Draw(vertexCount, vertexStartOffset);
    };
};

This works by having the VertexBuffer and IVertexBuffer being set by a call to IRenderUtility, before the IRenderUtility::Draw call.

Then, in your application will only need to load the RenderInterface project, and api implementation. There are other ways to do this, such as a #define'ing code throughout your code base, but imo, that is just messy.

Upvotes: 5

flyx
flyx

Reputation: 39688

First, I don't think it's worth the effort to implement both an OpenGL and a DirectX backend. You'd just be using the same hardware through different APIs. I'd go for OpenGL because it's an open standard and supported on more operating systems - including Windows, where DirectX is available.

Second, you should understand the differences between OpenGL (or DirectX) versions. Usually, a new version adds new features and deprecates some old features - which are still usable though. Rarely, old functionality is actually abandoned, as e.g. has been done with the fixed function pipeline in DirectX 10. This is another reason why I'd suggest to rather use OpenGL - it's more backwards compatible and you can more easily support multiple versions.

Supporting a newer OpenGL version usually means that you are using new features that weren't available in older versions. To stay compatible with older versions, you basically have two options:

  • Provide two implementations for the part of your application using OpenGL features that are not available in older versions. You then have to write an alternate implementation for the feature that only uses OpenGL features that are available in older versions.
  • Disable the functionality that uses new OpenGL features if the features are not available. This usually means that some effects are not rendered when the OpenGL version does not support them. This is not an option for functions of your rendering engine that are required and not just for "looking better".

If you want to support both DirectX and OpenGL, you'd have to provide a complete abstract interface for your rendering subsystem and probably three implementations - DirectX 9, DirectX 10 / 11 and OpenGL. As mentioned, you can compensate the backwards-compatible API updates within your implementation.

Upvotes: 2

ApprenticeHacker
ApprenticeHacker

Reputation: 22011

Use The Preprocessor

One solution to this, is to use the Preprocessor.

#ifdef DIRECT_X
// something for DX
#else
// something for OpenGL etc.
#endif

This method is also used by libraries like SDL. If you want to use a render system, simply #define it. Also, from cross-platform compatibility, you may need to use platform-specific macros. See this.

Use Seperate Translation Units

A better way, is to to put platform specific methods into separate translation units. This will allow cleaner & more readable code and easier to develop and maintain.

For example:

class Rectangle
{
     public:
         void draw(int,int,int,int);
};

rectangle_dx_9.cpp:

#include "rectangle.hpp"
void
Rectangle ::
draw(int upper_left_corner_x, int upper_left_corner_y,
            unsigned int length, unsigned int width)
{
// Direct X 9 specific code here.
}

rectangle_open_gl.cpp:

#include "rectangle.hpp"
void
Rectangle ::
draw(int upper_left_corner_x, int upper_left_corner_y,
            unsigned int length, unsigned int width)
{
// OpenGL specific code here.
}

etc.

Now all you have to do is, for Direct X 9, configure your build process to use rectangle_dx_9.cpp, while for OpenGL use rectangle_open_gl.cpp.

Upvotes: 3

Related Questions