D3D11 and OpenGL 4.2 wrap

Started by
2 comments, last by Juliean 10 years, 4 months ago

I'm not experienced OGL programmer. I've got a small rendering engine that uses only D3D11. I want to add OGL 4.2 rendering support.

I want some adivices on how to "attack" the situation.

I want to be able to switch the D3D11/OGL mode by changing a macro #define RENDERER DX11/OGL.

Im asking for some tips and tricks, some big differences between both APIs that could be a problem(something like deffered device, command lists, and multithreaded resource creation).

Advertisement

The fact the OpenGL doesn't use a device you have to pass around as DirectX does makes it probably more easy to add OGL to an application rather than the other way around. Do you already have some sort of wrapper around the API, or did you just use plain DX11 calls up until now? Most high-level constructs are still valid between those two, like textures, meshes, etc... so I would advice you to write wrappers around those. Something like binding a texture or rendering a model can be abstracted away behind a solid interface.

One thing that can definately become a problem is the different shading languages, which pretty much force you to write all your shaders twice or use an intermediate language like cg. Since DX11 uses cbuffers for shader constants/uniforms, it would be wise to look up OGL uniform buffer objects/blocks right away, unless you are willing to accept fundamental differences between the way you set constants in eigther API.

Again, the important part IMHO is to write an abstraction that doesn't need to know which API lies underneath that you call in your actual code for e.g. lights, shadows, etc... . Then, you can handle API differences under the hood, and probably add another level of abstraction if dealing with pure commands of eigther API annoys you too, if you will. To show you an example, take a look at this:


        enum class MuteFlags
        {
            READ, WRITE, READ_WRITE, WRITE_DISCARD
        };

        class ITexture
        {
        public:

            virtual ~ITexture(void) {};

            virtual void Lock(LockFlags flags) = 0;
            virtual void Unlock(void) = 0;

            virtual void Resize(const math::Vector2& vSize) = 0;

            virtual const math::Vector2& GetSize(void) const = 0;
            virtual const AclTexture& GetAclTexture(void) const = 0;
            virtual gfx::TextureFormats GetFormat(void) const = 0;

            virtual void* GetData(void) const = 0;
            virtual unsigned int GetPitch(void) const = 0;
        };

Thats my texture interface that every API implements. "AclTexture" is the actual low-level wrapper texture type, this could probably be solved in a better way. It doesn't even have to be an interface, I decided to go this way in order to allow plugins to be buildt API-independantly. As you can see, I have most actions that are required, like lock and unlock and access to the texture data. In DX9/DX11 those methods do eigther the lock or the map, yet since OpenGL doesn't require locks, those methods are implemented empty in the OGL-implementation. They are still being called when access to texture-data is wanted in the application layer, for compatibility between all APIs. GetData() will return the mapped buffer in case of DX, and for OGL I have a seperate buffer on the CPU side with texture data that I return here. Actually, in the Unlock-call in OGL there is an implementation, namely will it move the data on the GPU from the CPU-side buffer.

Hope this example gives you an idea how abstraction between APIs can happen.

Thanks a lot! There are good adivces.

Well for the resources I was thinking something like


enum EResourceUsage{
RESOURCE_VERTEX_BUFFER,
RESOURCE_TEXTURE_2D,.....
};

struct GraphicsResource
{
#if D3D_RENDERER
 typedef ID3D11DeviceChild* NATIVE_RESOURCE_TYPE;
#else
typedef int NATIVE_RESOURCE_TYPE;

private : 
EResourceUsage                  m_usage;
NATIVE_RESOURCE_TYPE m_resource;
}

and later on;

class TextureResource{


GraphicsResource m_resource;
}

But im not shure is that the level of abstraction that I want. Generally I want to be able to do something like this


Some custom case
#if RENDER_D3D11
GraphicsDevice()->GetD3D11NativeDevice()->Something();
#else
glFoo();

There are also a lot of enum to write. I've got 2 things in my head :


enum PRIMITIVE_TOPOLOGY
{
#if defined RENDER_D3D11
PRIMITIVE_TOPOLOGY_TRIANGLELIST = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
#else
PRIMITIVE_TOPOLOGY_TRIANGLELIST = PT_TRIANGLES
#endif
};

VERSUS


enum PRIMITIVE_TOPOLOGY
{
PRIMITIVE_TOPOLOGY_TRIANGLELIST = 5,
};


#if defined RENDER_D3D11
D3D11_PRIMITIVE_TOPOLOGY PRIMITIVE_TOPOLOGY_to_D3D11(const PRIMITIVE_TOPOLOGY& pt)
{
switch(pt)....
}#else
...

Well the first method looks better, but there are maybe some pitfalls? Do you have any sigestions about this?


Well the first method looks better, but there are maybe some pitfalls? Do you have any sigestions about this?

IMHO, you are thinking way too low level. It sounds like you are using native API calls through your application, which is, again just in my oppinion a bad choice since this leads to harder to maintain code. Of course, I'm using #defines to for choosing the correct API, but I hardly require any use of them actually in the clients code. The advantage of this is that I can write code using my own API, and whether I use OpenGL or DirectX it usually runs right away with little tuning needed. As for your examples:


Some custom case
#if RENDER_D3D11
GraphicsDevice()->GetD3D11NativeDevice()->Something();
#else
glFoo();

Why have it like this? Why don't have:


void Wrapper();

// somewhere hidden in a cpp-file

void Wrapper()
{
#if RENDER_D3D11
GraphicsDevice()->GetD3D11NativeDevice()->Something();
#else
glFoo();
}

Then you basically have one call, and whether API you use the call is made correctly. Do you see what I'm talking about? It seems like you want to cluster your code with case-handling about which API to use, when really you'd much rather have abstraction classes and/or functions to handle this kind of stuff. On your lowest level, hidden way behind those high level abstraction classes, you can have different classes for DX or OGL. That way, instead of having an interleaved format like:


enum PRIMITIVE_TOPOLOGY
{
#if defined RENDER_D3D11
PRIMITIVE_TOPOLOGY_TRIANGLELIST = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
#else
PRIMITIVE_TOPOLOGY_TRIANGLELIST = PT_TRIANGLES
#endif
};

you'd make your own independant enum


enum class PRIMITIVE_TOPOLOGY
{
      TRIANGLE_LIST, LINE_LIST
};

And use this on the high-level to interact with say your mesh. Instead of doing


#ifdef RENDER_D3D11
D3DMesh mesh(PRIMITIVE_TOPOLOGY_TRIANGLELIST);
#else
OGLMesh mesh(PRIMITIVE_TOPOLOGY_TRIANGLELIST);
#endif

you'd then do


IMesh* pMesh = CreateMesh(Topology::TRIANGLE_LIST);
pMesh->FillVertices(...); // this calls implementation is dependant on the selected API based on which child class was returned, but you don't need to have some #define here.

// factory function:
IMesh* CreateMesh(Topology top)
{
#ifdef RENDER_D3D11
return D3DMesh(top);
#else
return OGLMesh(top);
#endif
}

If you are concerned about speed or likewise, you can still typedef the currently used mesh instead of using a parent IMesh-type, but the point is, you really don't want to use those #ifdef-directives in your client-code, and you also want to reduce the usage of them in your low-level facilities.

This topic is closed to new replies.

Advertisement