1

I'm in the middle of programming a pure abstract interface that is capable of handling rendering in either Direct3D 11 or OpenGL 3 (or greater). The design basically looks like this:

// Abstract resource class
class IBuffer
{
public:
    // Destructor
    virtual ~IBuffer() { }

    // some pure virtual functions....
};

// Acts as a proxy class for ID3D11Buffer,
// on destruction calls COM Release()
class CBufferD3D11 : public IBuffer
{
public:
    // Construction and destruction.
    CBufferD3D11(ID3D11Buffer* buffer);

    // Releases the D3DResource
    ~CBufferD3D11();

    // Just left public for demo
    ID3D11Buffer* m_resource;   
};

// Abstract rendering class
class IRenderer
{
public:
    // Virtual destructor
    virtual ~IRenderer() {}

    // Factory function
    static IRenderer* Create(RenderType type);

    // Function to create a vertex buffer
    virtual IBuffer* CreateBuffer() = 0;

    // Function to enable a vertex buffer
    virtual void Enable(IBuffer* pBuffer) = 0;
};

// Acts a proxy class for the device object of Direct3D
class CRenderDevice : public IRenderer
{
public:
    // Constructor to create a rendering device
    CRenderDevice();

    // Function to enable a vertex buffer
    void Enable(IBuffer* pBuffer)
    {
        // This is a down cast, it could use dynamic_cast.
        // However this would be slow :(
        CBufferD3D11* pD3DBuffer = reinterpret_cast<CBufferD3D11*>(pBuffer);
        m_pContext->IASetVertexBuffers(0, 1, &pD3DBuffer, 0, 0);
    }
private:
    ID3D11Device* m_pDevice;
    ID3D11DeviceContext* m_pContext;
};

// Usage
void Foo()
{
    // Create the renderer which can then create a D3D11 buffer
    IRenderer* pRenderer = IRenderer::Create(D3D11);
    IBuffer* pBuffer = pRenderer->CreateBuffer();

    // Later during rendering
    pRenderer->Enable(pBuffer);
}

The issue I am having with the above design is the communication between the two abstract interfaces. The rendering device needs to know which resource to enable/render, but unfortunately due to the abstraction, the underlying Direct3D layer is only aware of a higher level interface that has been passed into the IRenderer::Enable function.

I have looked into using design patterns, but can't quite figure out which one would be the most suitable for use in future multi-threaded rendering. This would compromise of multiple device contexts that build rendering command lists and are played back on an immediate context [producer consumer].

So far the most efficient and thread safe method that I can think of is the one that uses down casting, either via a reinterpret_cast or through a lightweight custom RTTI. This keeps the abstraction light and not to far from the API implementation. As a result, the the application programmer is able to utilise additional functionality of the rendering pipeline, should they need to.

What is the best way to make abstract interfaces communicate together on a lower level without the need of down casting? What do commercial game engines tend to do?

I have looked into open source engines, but I am really not convinced their implementations are suitable for my needs. The best I've seen so far has been on David Eberly's site, which uses the higher level resources as a key to a std::map.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Sent1nel
  • 509
  • 1
  • 7
  • 21

1 Answers1

0

I'm not 100% sure I understand the problem here. If the bit you're struggling with is how to access the internal resource pointer, why not add a method to IBuffer to return a void* ?

// Abstract resource class
class IBuffer
{
public:
    // Destructor
    virtual ~IBuffer() { }

    // Grab a pointer to the internal resource
    void* GetResource() = 0;
};

// Acts as a proxy class for ID3D11Buffer,
// on destruction calls COM Release()
class CBufferD3D11 : public IBuffer
{
public:
    // Construction and destruction.
    CBufferD3D11(ID3D11Buffer* buffer);

    // Releases the D3DResource
    ~CBufferD3D11();

    void* GetResource() { return m_resource; }

private:
    ID3D11Buffer* m_resource;   
};

You can then cast that to the buffer type you're expecting to handle. So this:

    CBufferD3D11* pD3DBuffer = reinterpret_cast<CBufferD3D11*>(pBuffer);
    m_pContext->IASetVertexBuffers(0, 1, &pD3DBuffer, 0, 0);

...becomes:

    CBufferD3D11* pD3DBuffer =(CBufferD3D11*)(pBuffer->GetResource());
    m_pContext->IASetVertexBuffers(0, 1, &pD3DBuffer, 0, 0);

As long as your renderer is paired with the correct buffers this should work okay right?

I would think handling thread safety should stay a separate issue to worry about? i.e. wrap calls to buffers in suitable protection.

Jon Cage
  • 36,366
  • 38
  • 137
  • 215
  • Yea, the thread safety part should not be an issue so long as a design pattern, such as a message pattern, is not used. – Sent1nel Feb 23 '12 at 02:42
  • The issue I have with the solution you've provided is that it now exposes the resource, albeit in void pointer form, to the high level programmer. At no point should the high level programmer have use of this resource as he/she could accidentally do something unexpected with it, or be confused by it's purpose. There is also the issue that the abstraction problem has only been shifted somewhere else but still resides within the design. – Sent1nel Feb 23 '12 at 02:46
  • You could make that method a private one and make IRenderer a friend of IBuffer: http://stackoverflow.com/questions/17434/when-should-you-use-friend-in-c ...and make the `GetResource` function a private member. That would make it clear that `GetResource` is an internal function which API users needn't worry about. – Jon Cage Feb 23 '12 at 08:04