Reputation: 281
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
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
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
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:
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
Reputation: 22011
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.
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