【问题标题】:Guidance Required On Using Abstract Interfaces And Down Casting使用抽象接口和向下转换所需的指导
【发布时间】:2012-03-13 10:44:47
【问题描述】:

我正在编写一个能够在 Direct3D 11 或 OpenGL 3(或更高版本)中处理渲染的纯抽象接口。设计基本上是这样的:

// 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);
}

我在上述设计中遇到的问题是两个抽象接口之间的通信。渲染设备需要知道启用/渲染哪个资源,但不幸的是,由于抽象,底层 Direct3D 层只知道已传递到 IRenderer::Enable 函数的更高级别的接口。

我已经研究过使用设计模式,但无法确定哪一种最适合在未来的多线程渲染中使用。这将损害构建渲染命令列表并在即时上下文 [生产者消费者] 上播放的多个设备上下文。

到目前为止,我能想到的最有效和线程安全的方法是使用向下转换的方法,无论是通过 reinterpret_cast 还是通过轻量级自定义 RTTI。这使抽象变得轻巧,并且与 API 实现相距不远。因此,应用程序员可以根据需要利用渲染管道的附加功能。

在不需要向下转换的情况下,使抽象接口在较低级别上相互通信的最佳方法是什么?商业游戏引擎倾向于做什么?

我研究过开源引擎,但我真的不相信它们的实现是否适合我的需求。到目前为止,我看到的最好的是 David Eberly's site,它使用更高级别的资源作为 std::map 的键。

【问题讨论】:

    标签: c++ oop design-patterns rtti


    【解决方案1】:

    我不是 100% 确定我理解这里的问题。如果您正在努力解决的问题是如何访问内部资源指针,为什么不向 IBuffer 添加一个方法以返回 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;   
    };
    

    然后您可以将其转换为您期望处理的缓冲区类型。所以这个:

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

    ...变成:

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

    只要您的渲染器与正确的缓冲区配对,这应该可以正常工作吧?

    我认为处理线程安全应该是一个单独的问题来担心?即将对缓冲区的调用包装在适当的保护中。

    【讨论】:

    • 是的,只要不使用消息模式等设计模式,线程安全部分就不应该成为问题。
    • 我对您提供的解决方案的问题是它现在向高级程序员公开了资源,尽管是空指针形式。高级程序员在任何时候都不应该使用这个资源,因为他/她可能会不小心用它做一些意想不到的事情,或者被它的目的弄糊涂。还有一个问题是抽象问题只是转移到了其他地方,但仍然存在于设计中。
    • 您可以将该方法设为私有方法,并使 IRenderer 成为 IBuffer 的朋友:stackoverflow.com/questions/17434/… ...并使 GetResource 函数成为私有成员。这将清楚地表明GetResource 是一个 API 用户无需担心的内部函数。
    猜你喜欢
    • 1970-01-01
    • 2010-09-24
    • 1970-01-01
    • 1970-01-01
    • 2015-01-29
    • 2017-11-15
    • 2018-05-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多