8
\$\begingroup\$

I've seen lots of questions around this area but not this exact question so apologies if this is a duplicate.

I'm making a small 3d game. Well to be honest, it's just a little hobby project and likely won't turn out to be an actual game, I'll be happy to make a nice graphics demo and learn about 3d rendering and c++ design.

My intent is to use direct3d9 for rendering as I have some little experience of it, and it seems to meet my requirements. However if I've learned one thing as a programmer it's to ask "is there any conceivable reason that this component might be replaced by a different implmentation" and if the answer is yes then I need to design a proper abstraction and interface to that component. So even though I intend to implment d3d9 I need to design a 3d interface that could be implemented for d3d11, opengl...

My question then is what level is it best to do this at? I'm thinking that an interface capable of creating and later drawing

  • Vertex buffers and index buffers
  • Textures
  • Vertex and Pixel "shaders"
  • Some representation of drawing state (blending modes etc...)

In other words a fairly low level interface where my code to draw for example an animated model would use the interface to obtain abstract vertex buffers etc. I worry though that it's too low level to abstract out all the functionallity I need efficiently.

The alternative is to do this at a higher level where the interface can draw objects, animations, landscapes etc, and implement them for each system. This seems like more work, but it more flexible I guess.

So that's my question really, when abstracting out the drawing system, what level of interface works best?

\$\endgroup\$
1
  • \$\begingroup\$ It may not answer your question entirely, but I would advise looking at sources of DirectX+OpenGL engines like OGRE and Irrlicht to see how they've abstracted it. \$\endgroup\$ Commented Mar 5, 2011 at 17:51

6 Answers 6

8
\$\begingroup\$

Apologies for the length of this response!

"is there any conceivable reason that this component might be replaced by a different implmentation" and if the answer is yes then I need to design a proper abstraction and interface to that component.

"Any conceivable reason" is far too extreme a stance to take. Almost every function could be parameterised in some way, every module given some different strategy or policy, and every constant tweaked. If you provide an extra level of abstraction for each of these then you end up writing 2x as much code for no immediate gain, just a potential gain later if you ever require that flexibility.

If you go down the route of providing interfaces so that each of these things can be a facade for one of several different options, you then have to decide how you supply these different options, including defaults, and when you try and make that as simple as the original object was you often end up with a whole extra layer on top. This can (and does) get absurd in a lot of real world 'enterprise' software - may I humbly suggest reading this satire piece which sadly hits too close to home in many cases: Why I Hate Frameworks.

The biggest argument against this is complexity. Let's make the (probably incorrect) assumption that a good interface exists that could apply equally well to two 3D rendering APIs, and that someone is able to describe this to you in an answer here. You would then be working to this interface, which would, due to its multi-purpose nature, undoubtedly cover elements that DX9 does not use - for example, the plethora of API extensions that are available in OpenGL but not in DirectX 9. This is an added complication that will make your code harder to understand and maintain, and slow your learning process. It's bad enough when using an existing multi-platform library such as SDL, because people are forever coming across odd functions and restrictions that exist purely to fix something on one platform, but you'd not only have to learn why that part of the interface is there but also how it may or may not interact with the parts that you are currently about to write. eg. You'd end up writing abstract objects that encapsulate aspects of OpenGL extensions that are necessary in your core rendering code, even though you have no way of using them yet.

However, in future, once you are competent with how the DX9 system works, you can look into how the OpenGL system operates. You will then see how you need to adapt your existing system to make both parts work, and will do so, with the handy existence of your existing DX9 code path as an effective unit test to ensure your refactoring is correct.

You are lucky in that you are working with one of the most fluid and malleable materials known to engineering - source code stored as computer data. Embrace that blessing by making changes to it when you know you need them, rather than doing them months in advance and burdening yourself in the meantime by having to write to an abstraction you aren't gaining from yet.

\$\endgroup\$
1
  • \$\begingroup\$ This is how I feel, but at the same time the common advice I see is that abstracting platform-specific code for a game is easy to do and should be done... I don't know what to think. \$\endgroup\$ Commented May 27, 2012 at 9:13
5
\$\begingroup\$

However if I've learned one thing as a programmer it's to ask "is there any conceivable reason that this component might be replaced by a different implmentation" and if the answer is yes then I need to design a proper abstraction and interface to that component.

This isn't really answering your question, but from my experience as a programmer, premature generalization is just as bad as premature optimization. You rarely gain anything and have to maintain it over the course of the project.

My advice is to straight up use D3D9 client code in your project, and when you determine that you need to switch interfaces, make an interface with all the things you're currently using. Programmers are terrible at predicting what they might need, doubly so in your case where you're not terribly familiar with the material, so this way you're doing the least amount of work.

See also: http://c2.com/xp/YouArentGonnaNeedIt.html

\$\endgroup\$
3
  • \$\begingroup\$ The trouble with this is that if he later has to go for an abstraction, he then has to re-write all his interface code. And if D3D9 was globally included throughout his project, it's going to be damn hard to refactor all that code. \$\endgroup\$
    – DeadMG
    Commented Mar 5, 2011 at 18:26
  • 1
    \$\begingroup\$ He's going to have to rewrite everything anyway when he has a use case that isn't covered by whatever assumptions he makes now anyway, though. Refactoring should be embraced, especially for a learning project. \$\endgroup\$
    – Tetrad
    Commented Mar 5, 2011 at 19:30
  • \$\begingroup\$ I'm exagerating the case a bit. I too hate unnecesary abstraction. But in this case I can say actually there is a good chance I'll want to do this with a different implementation... \$\endgroup\$
    – JohnB
    Commented Mar 6, 2011 at 16:46
4
\$\begingroup\$

Hm, looks like I don't have enough reputation to post comments to responses.

Both Kylotan and Tetrad gave excellent answers. What I would add is to suggest you separate rendering code from the game logic -- this will serve several purposes:

  • your code will be cleaner
  • it will be easier for you to make changes to your renderer without rewriting half of the game logic each time
  • eventually, providing completely different rendering modules is easier this way

As an added bonus, this way you will build your abstraction gradually as you work on your project. As you say, this is a learning project so take it easy and make it simple, adding complexity and abstractions as you go instead of making grand plans as you probably lack expertise to get it right initially (as Tetrad also suggested).

\$\endgroup\$
2
  • \$\begingroup\$ Oh indeed! But the question is what level do I separate it? At the level of drawing a whole town, or the level of drawing a triangle? Or where in between \$\endgroup\$
    – JohnB
    Commented Mar 6, 2011 at 16:48
  • \$\begingroup\$ A reasonable way would be to keep rendering API calls separate. That would probably be about 'drawing a building or a car' in your analogy (considering your buildings and cars are single objects :) ). An example: you want to animate your object (for simplicity let's assume only one can be played at a time). A game object could have a list of available sequences and their length and set the active animation and its speed while the renderer would manage, well, the rendering part. You may also need to obtain data back from the renderer, e.g. object's collision shape may change during animation. \$\endgroup\$
    – Gilead
    Commented Mar 7, 2011 at 22:27
1
\$\begingroup\$

You can't write a low-level abstraction over this kind of thing, because the details of a shader are completely different between D3D9, 11, OGL, etc. It's simply impossible.

You could check out th abstractions implemented in, say, SFML, SDL, OGRE if you want to see what other people have chosen to do for rendering abstractions.

\$\endgroup\$
1
1
\$\begingroup\$

I've tried to write an API that wraps around both D3D and OGL neatly. It's a massive undertaking, I learned. Many of their differences can be reconciled. For instance, loading shaders. I effectively encapsulate D3D and OGL shader code in my own format. IE: In D3D, you need to specify the shader profile, so the file itself has the shader profile written in it. When the encapsulateed shader source is handed to my API, it calls the appropriate D3D shader creation method with the appropriate shader profile and such.

However, there are still plenty of differences that I haven't solved, nor might I ever, because I've decided that a higher-level abstraction is easier to design and satisfactory for my requirements. I'm currently working on something sort of similar to the D3DXEffect framework. Objects have a render-technique, which contains render passes, which have a set of render states, a set of shaders, and require certain inputs. This allows me to abstract more of the API away.

\$\endgroup\$
1
\$\begingroup\$

I don't know why people automatically jump to premature generalization/optimization when questions like these pop up. There is something to be said about being preemptive and realizing when a component in a system is likely to be swapped out. All of the answers about "don't worry and code away till you need to change it" generally get people into trouble in the real-world since they bring about bigger changes down the road, or leave behind code that should been designed differently to start with, but was left in because it "works". Doing some thinking ahead of time and knowing where to stop before you over-think is a skill in itself.

It would be much easier to refactor interfaces to accommodate differences between DX/OGL, than to introduce a whole new implementation (DX for example) into a system that is strictly coded to work with OGL. Code abstraction should happen at some level anyways as you wouldn't want to be mixing graphics code with game logic code all over the place, or repeating code. If I'm working on a game and starting out with OpenGL (because I have experience with it), but also know that I'd like to eventually get the game on Xbox/Windows (DX), it would be silly for me to not take that into consideration when designing my graphics API. The idea of refactoring something as complex as a game, and introducing new graphics APIs is tedious and error prone. I remember listening to an interview with Tommy Refenes (Super Meatboy) where he speaks about having to port the game to Mac after completion, and going through absolute hell because of lack of planning ahead of time.

You should consider how you're going to use your API, and build it up as your needs/knowledge change during the game's development process. You're not doing an abstraction over low-level OGL/DX, but rather creating an API for yourself to aid in loading geometry, loading/compiling shaders, loading textures, etc.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .