Graphics Engine using *any* graphics library...but how?

Started by
5 comments, last by serratemplar 17 years, 4 months ago
Incase the subject was cryptic, here is my intention: Say I want to build a graphics engine that can use either OpenGL or Direct3D. At its most basic level, when the graphics engine has to call a draw function in whichever library was selected (in set up, for instance), what sort of structure or mechanics do I need? Or a sprite engine, using either DirectDraw or SDL or Windows GDI? Or say I'm truly crazy and want to support all five? (Please disregard 3d to 2d stuff in this example...let's say it's all 2d.) Function pointers? I thought maybe I could write the appropriate functions to draw/interface with each library, then at start up set the functions pointers up that would be used (as well as the different data they'd need to be called with...yuck). That was my first guess, but then I thought some sort of inheritance/OOP thing might work too (if the OOP overhead wasn't too bad...is that something I even have to worry about?). It occurred to me that this has been done before, and there may be a decent tutorial or article on this out there somewhere. What kind of new programming should I be learning and where can I learn it, so that I may do this? =) Thanks in advance!
Advertisement
I would look up the facade design pattern. In my engine, I have a RenderSystem object, which instantiates either a DXRenderer or GLRenderer object depending on what the engine settings are at runtime. Objects that need to be rendered don't directly interact with the DX or GL renderer, they do so through the RenderSystem facade object, which has methods for rendering primates (point, line, polygon, mesh...). When an object needs to draw itself, it makes a call like this:

RenderSystem->DrawMesh(mesh, this->GetTransform());

The objects that are drawing themselves never know which rendering library is being used, because all of the library specific calls are in either DXRenderer or GLRenderer.

On a side note, DirectX and OpenGL use different matrix ordering. DX is row major while OpenGL is column major. You'll want to build in a transposition function into your matrix class.
Will Miller | Game Designer | Big Huge Games
I've been toying around with this idea too, mostly as one of those fun research projects that would maybe teach me something about the underlying graphics libraries than how to actually make something of commercial quality. The best I've come up with is to define an interface. Define what operations you'll need to actually do. Let's call that IGraphicsEngine, or whatever you want to call it. Then have something like OpenGLGraphicsEngine that inherits from IGraphicsEngine and so forth. Then with each bit of "function"ality you add in, you learn how to do that concept in the underlying graphics api. Then at application load time, you could have a setting saved off in a file or the registry (or system equivalent if you're not on Windows) that indicates which library you want. Then you initialize the correct type of renderer at that time. Heck, maybe you could even figure out a way to switch which one is going at run time. I don't know.

I have not thought about the performance hit of this sort of thing, and I'm mostly thinking aloud. I admit I'm a total newb (noob? What is the offical spelling on that?) with OpenGL, DirectX, and <insert graphics library...>. Seemed like it would be a fun toy project though.

*edit* - dang... took too long to post!
Same as what juanpaco said above where you could also use the factory design pattern to get back the right instance.

IGraphicsEngine engine = GraphicsEngineFactory.GetEngine();

A wise man can learn more from a foolish question, than a fool can learn from a wise answer - Bruce Lee
This is pointless, unless you're doing it purely for the academic sense of trying it. It's not very productive, so if you're doing this for any other reason (read: you're trying to make a game), forget about it. If you are just doing this to experience the...joy...of the process, then you may skip down to the bottom of my post.

Still here? Okay.
This is dumb because it takes a lot of time and effort, and gives you vanishly small benefits. Making the render API choice at runtime is especially useless; it is inherently not something you should be doing at runtime. Doing it at compile time is still generally a waste of time, especially for non-professionals, because the effort still outweighs the gains.

A well-designed renderer will be easy to port (statically) to a new render API. But trying to develop both at the same time will likely not result in a well-designed API -- or at least, not a well-designed API as far as rendering goes. It might be a great API as far as switching out the underlying functionality goes -- but that's not what you want in a rendering API, is it? Chances are, however, you'll just end up with a mess, a clunky API that performs modestly using either underlying API, rather than a streamlined API that performs well using one.

To close, I'll quote Promit's response to the decision to make a renderer abstract from another thread:

Quote:
Originally posted by Promit:
[This is] about the worst choice you can make.

First of all, as an indie/hobbyist developer, that's a staggering waste of your time. I've been there. There aren't any real advantages, but you will spend many hours struggling to make it work. There are more interesting and more productive things to spend your time on. About the only thing to be gained here is that it might force you to design slightly better than if you hadn't written multiple API support -- but you can learn that sort of design while still doing more immediately useful things.

Second, as a professional developer, that's a staggering waste of your time. Every API you add introduces bugs into the system. Debugging becomes a chore because you have to determine what API is causing the bug, or if the bug is higher up. You've neatly gone and doubled the likelihood for bad interactions with the driver, while simultaneously halving the amount of time available to debug and test those interactions. By making the API chosen an inherent part of the platform, you make debugging much easier, and porting becomes easier as well.

Third, this inevitably leads to virtual inheritance hierarchies in an attempt to hide both APIs. Don't bother. It's cleaner and simpler to simply port your core renderer over completely, and to maintain a separate build for each platform. If your design is properly done, you can simply switch the rendering core out from under the game and it won't notice. (Whether this switch is handled purely at build time, or if you can do it at runtime by loading different DLLs, is up to you.)


That said, if you must:
The suggestions provided by the above posters are decent ways to start, though I would strongly caution against making the API choice a runtime decision. It is fairly easy to implement the switch at compile time, based off a preprocessor constant or something.

The simplest example would be a single header that defines an interface that is implemented by two different .cpp files that are guarded by appropriate preprocessor commands. Some preprocessor magic, or pimpl-like implementations, will be required to hied API-specific types in private implementation details contained in the header (for example, your public headers can't contain, or must guard the use of, IDirect3DDevice and related #include directives), but this is relatively simple to set up if you think about it carefully.
Before continuing (and give you some possible solution), I want you to ask yourself why you want this. If your goal is portability (and I don't see many other possible goals), OpenGL is just the solution you want. If you want to do this "because it's kinda cool", then you should probably reconsider your idea, for an obvious reason: you'll have to write that code, meaning that you'll double your development time, without any visible benefits. Even more, doubling the size of the code will probably double the number of bugs - which is probably not what you want. You'll also have to make sure that the rendering is very similar on both platform, so it will add a tremendous testing time to your development schedules. And, again, the gain is minimal. So why do you really want to do it?

Ok. Enough for the OT questions, let's deal with the design.

The problem here is that even if OpenGL and DirectX share some basic principles, there are a lot of difference in philosphy between these two major API (for example, the device lost problem which is not present in OpenGL). Implementation details also vary (for example, the shader language is different and the matrix interpretaion is different too). The first conclusion we can draw is that we need to encapsulate these differences in a set of high level classes.

There is two possibilities to do so: the first one is to implement a class hierarchy (define a base interface A, implement this interface in concrete classes A_DX and A_OGL). It's probably the easiest way to go, but it will require to link your software with both OpenGL and DirectX. Of course, you can avoid this by using dynamic loading (either you dynamically load the OpenGL or DirectX DLL, or you dynamically load a my_renderer_dx.dll or a my_renderer_ogl.dll that are built with the corresponding API).

The other possibility (for whatever reason, this is the one I chosed) is to implement the renderers in two static libraries (one for OpenGL, another one for DirectX) and to statically link your program with the one you want. It allows me to avoid some typical class inheritance problems, as well as some extra runtime penaltie that otherwise can't be avoided (for example, matrix transpose is now uneeded, as the correct matrix class is tied to the renderer). It's not that difficult to implement (it requires you to be sure about your pathes and so on).

The second conclusion we can draw is that we need to encapsulate everything - and in turn this needs us to provide a powerfull abstraction on the top of the two API. But some behavior are not identical (for example, the DirectX render states and the corresponding feature of OpenGL are quite different). Of course, even the most powerfull abstraction has its limits, so some particular functionality will prove hard (if not impossible) to implement. You have to carefully chose what simplification you'll make if you don't want to fail (for example, by implementing an important functionality very inneficiently).

What abstraction should we build? First, remember that what we need to encapsulate at this point is really the renderers - we are not creating the game engine yet, we are only creating a 3D rendering engine. What are the typicall needs of a rendering pipeline? The question is easy: we must have some way to manage resources of different types (textures, render targets, vertex and index streams, shaders and so on); we must be able to send them to a renderer, whose functionning state (cull mode and so on) shall be modifiable. Special mathematical objects (vectors, matrices, ...) are needed as well to perform operations on the data. Ideally, we don't want to mess up with window management, but my opinion and you may not agree (in the engine I'm currently building, the user supplies a device that have already been created - he has the responsability to create the window and to manage it the way he want; the good thing is that I don't have to assume anything about how the window should be managed).

A quick though about this: make sure you are building a generic renderer abstraction. You don't want to build it with either 3D or 2D in mind, what you want to do is to build it so it will expose as much as it can.

What else? In fact, that's all. A renderer is quite a simple beast, and there's no need to over complexifying the task at this point. In order to simplify the design and the implementation, every thing will be done in immediate mode (as both OpenGL and DirectX works this way). It means that we won't deal with meshes at this step - but we might deal with them at a higer level, in the game engine (and that's another story).

The approach lead us to another conclusion: a new layer must be built on the top of this abstracted renderer in order to perform the action you want. But it is now far easier since you don't have to worry about the implementation details - you use your abstracted renderer, not a particular API. That way, you'll be able to build a more specialized engine that will entitle you to build a game.

Regarding some of your other questions: the OOP overhead exists, but it is negligeable (performance wise) in most cases. For example, a call to a virtual function typically needs two more asm instructions (a mov and a jmp - really nothing heavy). You don't have to worry about this.

I failed to address a point in your post: OpenGL/DirectX vs. DirectDraw/SDL/GDI. I purposedly spoke about OpenGL and DirectX only in order to handle the most difficult case: the construction of a 3D renderer. It is still perfectly valid to use the same approach with 2D (in this case, it would probably be better (from a design point of view)) to build the 2D layer on top of the 3D one for devices that can handle 3D). The class hierarchy would probably look like:
  2D game engine    |_______ GDI 2d rendering engine    |_______ SDL 2d rendering engine    |_______ DirectDraw 2d rendering engine    |_______ 2d over 3d engine               |_______ 3d rendering engine                            |_______ DirectX rendering engine                            |_______ OpenGL rendering engine

Again, remember that this is going to require a lot of work, with very little benefits over a single implementation.

HTH,
Thank you all for your responses.

I was under the impression that many professional games on the market run under both Direct3D and OpenGL (it's a command line switch or a config file modification for games like World of Warcraft, or a set-up option for Unreal Tournie)* and I presumed the reason for this was that on some systems, one runs "better" than the other.

I suppose that may simply be placebo effect / end-user perception kind of stuff, but it occurred to me as something worth learning about, and thus my motivation was academic.

*I understand well the projects I cited there enlist many orders of magnitude more programmers than Just Me By Myself =) so 'spending my time on more useful things' is very motivating to me. (I.E. putting it in the light of doubling my debug time has more or less dissuaded my interest in trying it.)

I think I'll file this one back into the theory bookcase in the back of my brain for now.

This topic is closed to new replies.

Advertisement