6
\$\begingroup\$

I would like to be able to move my game from using a XNA graphics device to possibly an OpenGL device in the future. So I'd like to have a somewhat abstract interface to submit polygons to. Its ok if it does not fully hide the directx pipeline. I simply want something that takes in a bunch of vertices and draws them.

I was wondering if anyone has done this and/or if you guys had any advice for me.

\$\endgroup\$
4
  • \$\begingroup\$ If you're using XNA, do you even have any choise of graphics device, really? \$\endgroup\$ Commented Sep 26, 2011 at 9:04
  • \$\begingroup\$ If you're even thinking "submit polygons" or "a bunch of vertices" you're already on the wrong track. \$\endgroup\$ Commented Oct 4, 2011 at 0:23
  • \$\begingroup\$ @Patrick Care to elaborate? (D3D11 only accepts vertex buffers, so you need to create it only once, and render it repeatedly) \$\endgroup\$
    – bobobobo
    Commented Oct 4, 2011 at 3:07
  • \$\begingroup\$ Buffers and formats and all that, those are details internal to an implementation. No one outside the interface needs to know anything other than an object name or handle and what you can ask it to do. So what I mean by being on the wrong track is that the abstraction is at the wrong level to be of any use. What if some of the new graphics were hulls completely calculated inside the shader, metaballs, or even VFX sprites. For a great example, consider Audio APIs; with audio you typically just say "play burp at xyz" and off it goes with no clue to the outside if it's wav or mp3 or what. \$\endgroup\$ Commented Oct 4, 2011 at 4:03

4 Answers 4

3
\$\begingroup\$

When you're dealing with vertex buffers, it gets real tricky with doing cross-platform or cross-API code, especially if you want to use the best approach particular to each API. I would look at some open-source cross platform engines such as OGRE to see how they do things at the lowest level- don't try to implement more complex ideas right away, like quadtree heightmap generation or cube mapping.

A good first step is to start porting your XNA code to DirectX 9 first. XNA really just hides many things of DX9 behind the scenes, and you will just have to learn how to work with unmanaged code. When you have the basics down of loading and reading resources in DX9, you can begin wrapping it around more general functions.

You have one benefit with OpenGL, though. It handles resources in a more managed way (just imagine all of them are in a managed pool). With DX9 you have to handle lost devices to take care of your resources.

\$\endgroup\$
3
\$\begingroup\$

(In the interests of disclosure: (1) I have not completed my implementation yet, so I cannot say with certainty how well it worked, (2) I only do graphics programming as a hobby, I write my engine and tools because I enjoy writing engines and tools, I don't know how well any of this would apply to a commerical project.)

I find this question very interesting as I have been working for the last couple of days on the same thing, and it certainly isn't for the feint-hearted (or people who like tidy code and lots of refactoring ;))

It all depends on where you aim; as Patrick Hughes commented so many of the types and concepts are very implementation specific that it is difficult to keep something satisfactorily generic and of use. That said, if too much is done in the plug-in the purpose of the abstraction is negated.

Take for example, a mirror or video screen in the game world, say you implemented this by rendering your scene using a special viewport to a texture on the GPU which was then provided to the shader of your mirror or screen with some post-processing. This I would say is an ideal task for the engine, pertaining to a collection of low level objects with specific configurations, with some knowledge of the scene at large - but if your layer is dealing only with complete game objects such as models, you'd have to implement all that logic once for every API your layer supported.

The very nature of an abstraction means you shall lose functionality the further away from the API you move, how generic your methods and objects depends on the requirements of your application (or probably more accurately, what specific features of your chosen API you want to use and interact with explicitly).

My recent attempt had one purpose, and that was to in some way 'protect' the work I put into an engine, by ensuring it would not be tied to a specific API, so I could go cross-platform, or support different grades of graphics cards. I chose a level slightly below that of XNA, and set some rules for the composition of my wrapper, such as, there shall be no internal caches - all objects shall be passed back through the layer (though they are hidden). I used namespaces and split up the projects so I could force myself to conform to these by omitting references to parts that other parts of the code should not see.

As I wanted to put all the interesting and tricky bits in an engine where they could be reused, I opted for a low level wrapper. I have a 'template' for a 'Renderer' (which abstracts the device) defined by an abstract class, which has methods to create a number of objects representing common paradigms such as Vertex/Index Buffers, Textures, Shader Programs, etc. Each of these implementing an appropriate interface.

The quick and dirty diagram below indicating whereabouts I intended my wrapper to sit:

My attempt and its position between DX, OPENGL, XNA and the Engine

An example excerpt from my 'template':

public enum ElementFormat
{
    Vector4 = 4 * 4,
    Vector3 = 3 * 4,
    Vector2 = 2 * 4,
    Byte4   = 4 * 1,
    Int     = 1 * 4
}

[Serializable]
public class VertexElement
{
    public string Semantic;
    public ElementFormat Format;
    public int Index;
}
public interface IVertexBuffer 
{
    VertexElement[] VertexDefinition { get; set; }
    void SetData(byte[] data, int destStartPosition);
    void GetData(byte[] data, int lengthDataToGet);
}

public abstract class Renderer
{

    /// <summary>
    /// Creates a new index buffer on the GPU, initialising to the supplied data.
    /// </summary>
    /// <param name="length">Length in bytes of the new buffer</param>
    /// <param name="data">Data to initialise to</param>
    /// <param name="dynamic">Buffer is accessible by the CPU. If no data is supplied this is assumed to be true</param>
    /// <returns>A handle to the new buffer</returns>
    public abstract IVertexBuffer CreateVertexBuffer(byte[] data, bool dynamic);
    public abstract IVertexBuffer CreateVertexBuffer(int length);
}

The actual object that is returned by CreateVertexBuffer() is defined within the wrapper, and the engine has no knowledge of it.

    public class Dx11VertexBuffer : IVertexBuffer
    {
        internal SlimDX.Direct3D11.Buffer DxBuffer;
        internal SlimDX.Direct3D11.VertexBufferBinding DxBinding;
        internal SlimDX.Direct3D11.InputElement[] DxVertexDefinition;
        internal int DxVertexStride = 0;
        internal DirectX_11_64_Renderer Parent;

        public void SetData(byte[] data, int destStartPosition)
        {
            Parent.UpdateBufferData(DxBuffer, data, destStartPosition);
        }

        private VertexElement[] vertexdefinitioncopy;

        public VertexElement[] VertexDefinition 
        { 
            get 
            {
                return vertexdefinitioncopy;
            } 
            set 
            {
                vertexdefinitioncopy = value;
                Parent.SetVertexFormat(this, vertexdefinitioncopy);
            } 
        }

    }

My wrapper abstracts away some detail, for example DirectX has a single type of Resource Buffer with various bindings for things such as vertices, indices, textures, shader constants, etc, whereas I specify multiple explicit types - the ones I intend to use.

There are of course serious caveats with this design:

  1. It is closely modelled on DirectX - other implementations will have to bend their APIs to a degree.
  2. It leaks - if there are some (desired) concepts that some APIs implement and others do not, the only option is to expand the template to accommodate them, and have the others break; the engine can work out what is available and what not and use workarounds if available.

I pursued the design for a number of reasons, that I would say are important to keep in mind when thinking about designing something such as this:

  1. Unless you work for Pixar, there are really only two APIs - DirectX and OpenGL (and if you work for Pixar, there are still only three)
  2. And both of these use very similar concepts (makes sense, afterall they operate on the exact same hardware!) - look at the 'SlimDx Pipeline' vs. the 'OpenTK Pipeline'.
  3. Most people use Windows, so optimizing for DirectX is never going to be a bad call. (I am writing this with a view to three implementations - OpenGL, DirectX 9 and DirectX 11)

In addition to that of course, is that I am building this as a tool for my personal use, designed only for the methods of the engine I build on top of it, meaning backwards compatibility or leaks are not as big a concern as they would be if it were public or operated at the same level as the engine - full vertical control gives you much more flexibility.

I would be interested to know how you get on, good luck!

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

Here's how I did it: its not practical for me to post the full solution here, but here is an outline:

// this class interacts with the Windows O/S
class Window { } ;

// This class contains the "contract" that each
// of the subsequently defined Graphics Windows
// MUST implement.  Provides basic service such as
// initialization, and rendering a SCENE* object
class IGraphicsWindow : public Window
{
    virtual bool init( int width, int height ) PURE ;
    virtual void render( Scene * scene ) PURE ;

    // ... MORE METHODS (not shown) ...
} ;

(a SCENE object contains a collection of Models and vertices.. it is required to be able to render a Scene object)

// each implements all methods required by IGraphicsWindow..
class D3D9Window : public IGraphicsWindow {} ;
class D3D11Window : public IGraphicsWindow {} ;
class OpenGLWindow : public IGraphicsWindow {} ;

// Then, you work with an IGraphicsWindow interface
IGraphicsWindow* window = new D3D11Window() ; // using d3d11 thru
// an IGraphicsWindow interface.

Its a lot more complicated than that. Handling vertex buffers is completely different, so I defined:

class VertexBuffer abstract
{
public:
  virtual void drawTris() PURE ;
  virtual void drawLines() PURE ;
  virtual void drawPoints() PURE ;
} ;

And then:

template<typename T> class D3D11VertexBuffer : public VertexBuffer { } ;
template<typename T> class D3D9VertexBuffer : public VertexBuffer { } ;

D3D9Window uses D3D9VertexBuffers, and D3D11Window uses D3D11VertexBuffer

So really, its hard and requires a lot of maintenance and upkeep (DING!!).

\$\endgroup\$
0
\$\begingroup\$

You might not have to change anything in your own code actually! The XNA API, apart from the HLSL shaders themselves, has very few ties with DirectX.

There are multiple projects reimplementing the XNA API on top of Mono with OpenGL, and making good progress. Here are a couple:

  • MonoGame supports 2D rendering (3D is on the way), for instance Terraria has been shown to run unmodified with Mono on Linux using MonoGame's DLLs. It also supports mobile deployments (iOS & Android through MonoTouch and MonoDroid)
  • ExEn is more centered on mobile but has started funding for OS X and 3D support.
\$\endgroup\$

You must log in to answer this question.

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