Abstraction layer between Direct3D9 and OpenGL 2.1

Started by
11 comments, last by Squared'D 11 years ago

I've been working on making my project able to use multiple APIs albiet for now just Direct X 9 and 11. My first attempt was to emulate DirectX 11 and make Direct X 9 fit to it. Later because I wanted it to be easier for others to use my coded, I decided on a more high level solution. I've found that both have their plusses and minuses.

At first, it all depends on whether you are fine with compile-time dependency for one of the APIs or actually want to be able to switch between them on run time. For latter, you are going to need a very complicated system of designing an API-agonstic layer atop all your other abstractions, but thats another topic. I just wanted to add that you don't really have to chooce between any of those two - I'd say you fare well if you sort of combine them too. I (before going API agnostic) used a two layer abstraction approach - one lower, and one higher level. That said, I have to disagree with some of your points:

Low Level (pick your favorite api and emulate it )
Good
* It's easier to build higher level functionality because everything has the same base
Bad
* As AgentC said, you can run into some non-optimal situations that could take some "hacks" to fix.

Why is it easier to build higher level functionality that way? It's probably that, if you don't follow the Dependency Inversion principle, it might be harder to build your functionality the other way round, but to say generalizing that the low-level approach is easier to build upon is just... wrong, at least IMHO.

High Level
Good
* You can code things in the most optimal way for the API.
Bad
* Without adequate planning it could almost be like coding two engines. You must be sure you isolate similarities or you'll end up coding many things twice.

Just one word: Interfaces! Whether a direct approach (java, ...) or the languege abstracted equivalent (C++, ...), if you design your API abstraction layer with those, and follow most importantly all patterns of the SOLID principles, it isn't really any "harder" than using a low level approach only. But you are right in a way, for an inexperienced programmer, it might be hard, given that those will possible meet the requirement of "no adequate planning" more easily. But going "low-level" isn't going to be easier for him in any way, and then again, a programming beginner shouldn't be aiming for making a multi-API "engine" anyway. At least in my opinion.

Once again talking about my way, I actually have a low-level encapsulation around the actual API I'm using (not interchangable, each abstraction is completely independant of any other API's abstraction) combined with a high level abstraction, that supplies interfaces like ITexture, IMesh, ISprite etc... . From those you would inherit your actual API dependand implementations, like DX9Texture, DX9Sprite, etc... . Now truly like you said, if one e.g. wasn't using interfaces properly, he might end up having to rewrite huge parts of his engine just to fit for the specifiy API. But that way, I'm taking advantage of both the low-levelish encapsulation and the high-level abstraction.

Advertisement
I read carefully all the replies, thanks for the tips.
I learned OpenGL 1.1 and 2.1 some time ago, but I don't now many things of Direct3D9 (for example the possibility to specify the vertex declaration without using FVF), the global shader uniforms (thanks mhagain for this information) and others. Fortunately the framework will be pretty tiny, it will support a single vertex and fragment shader (directly compiled and used during the initialization until the end of the application), specify the view matrix only during window resizing, no world matrix (I'm translating all the vertices from CPU side for specific reasons); the only thing that the framework should do, is to initialize the rendering device, set the view, create and/or load the texture and accept indexed vertices. I think that the "preferred API" can be a choice in application / games that requires a lot of performance with heavy 3D models and effects.
So until now, I've a virtual class with these members:
GetDeviceName(), Initialize, SetClearColor(float, float, float), RenderBegin (handles glBegin or pd3d9->BeginScene if it's fixed pipelne), RenderEnd (glEnd or pd3d9->EndScene() with the swap of the backbuffer), SendVertices(data, count, primitiveType), CreateTexture(id&, width, height, format), UseTexture(id) and SetView(width, height). All the works from the render device (like shader creation, uniform assignments, stores various textures in a single big texture, vertices caching, texture filtering, render to texture (glcopyteximage2d should works with GL 2.1<) etc) will be transparent, like a black box. The framework works fine with rotated sprites, cubes etc, the only weird thing that I noticed is that Direct3D9 handles the colors as ABGR and OpenGL as ARGB, but I can imagine that shaders will cover this issue.
So with big projects, a preferred API is chosen and during portings these APIs are wrapped, right? This remember me that Final Fantasy 1 for Windows Phone is a porting of Android version that it's also a porting of the iOS, that finally it's a porting of PSP version, in fact the game is... horrible. In cases of powerful engines that promises high performances like Unity, UDK etc, they program it directly with the low-levels things and during the porting they change most of the code without to wrap the original API?

Why is it easier to build higher level functionality that way? It's probably that, if you don't follow the Dependency Inversion principle, it might be harder to build your functionality the other way round, but to say generalizing that the low-level approach is easier to build upon is just... wrong, at least IMHO.

In my more low level implementation, I have about 4 or 5 basic interfaces (textures, vertex buffers, shaders, etc) Personally I found it quite easy to build higher level systems from this initial more low level abstraction. I also have another interface for handling states and draw calls. Now all I need to do is call these things and I'm fine. I still use this approach for myself. I can now just switch out implementations based on which API I want to use.

The more higher level approach was for code that I'd like to share because the interface is easier. (High level systems should have a cleaner interface in my opinion or what's the point.) I guess in the end, you are correct, I really shouldn't say either way is easier and I probably misspoke a little.

Learn all about my current projects and watch some of the game development videos that I've made.

Squared Programming Home

New Personal Journal

This topic is closed to new replies.

Advertisement