使用抽象接口和向下转换所需的指南

发布于 2025-01-08 05:36:48 字数 2336 浏览 4 评论 0原文

我正在编写一个纯抽象接口,该接口能够处理 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 的网站,该网站使用更高级别的资源作为关键到 std::map。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

夜光 2025-01-15 05:36:48

我不确定我是否理解这里的问题。如果您遇到的问题是如何访问内部资源指针,为什么不向 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);

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文