C++ 图形API上的包装器

C++ 图形API上的包装器,c++,opengl,graphics,directx,C++,Opengl,Graphics,Directx,我非常喜欢有一个能够适应的游戏引擎,不仅仅是它能做什么,还包括它如何处理新代码。最近,对于我的图形子系统,我编写了一个要重写的类,其工作原理如下: class LowLevelGraphicsInterface { virtual bool setRenderTarget(const RenderTarget* renderTarget) = 0; virtual bool setStreamSource(const VertexBuffer* vertexBuffer) = 0

我非常喜欢有一个能够适应的游戏引擎,不仅仅是它能做什么,还包括它如何处理新代码。最近,对于我的图形子系统,我编写了一个要重写的
类,其工作原理如下:

class LowLevelGraphicsInterface {
    virtual bool setRenderTarget(const RenderTarget* renderTarget) = 0;
    virtual bool setStreamSource(const VertexBuffer* vertexBuffer) = 0;
    virtual bool setShader(const Shader* shader) = 0;
    virtual bool draw(void) = 0;

    //etc. 
};
我的想法是创建一个在大多数图形API中通用的函数列表。然后,对于DirectX11,我只需创建一个新的子级

class LGI_DX11 : public LowLevelGraphicsInterface {
    virtual bool setRenderTarget(const RenderTarget* renderTarget);
    virtual bool setStreamSource(const VertexBuffer* vertexBuffer);
    virtual bool setShader(const Shader* shader);
    virtual bool draw(void);

    //etc. 
};
然后,这些函数中的每一个都将直接与
DX11
接口。我确实意识到这里有一层间接性。人们对这个事实感到厌烦吗


这是一种广泛使用的方法吗?还有什么我可以/应该做的吗?可以选择使用预处理器,但这对我来说似乎很麻烦。还有人向我提到了模板。你们怎么看?

如果虚拟函数调用成为问题,有一种编译时方法可以使用少量预处理器和编译器优化来删除虚拟调用。一种可能的实现方式如下:

class LowLevelGraphicsInterface {
    virtual bool setRenderTarget(const RenderTarget* renderTarget) = 0;
    virtual bool setStreamSource(const VertexBuffer* vertexBuffer) = 0;
    virtual bool setShader(const Shader* shader) = 0;
    virtual bool draw(void) = 0;

    //etc. 
};
使用纯虚拟函数声明基本渲染器:

class RendererBase {
public:
    virtual bool Draw() = 0;
};
声明一个具体的实现:

#include <d3d11.h>
class RendererDX11 : public RendererBase {
public:
    bool Draw();
private:
    // D3D11 specific data
};
还要创建一个标题
Renderer.h
,为您的渲染器包含适当的标题:

#ifdef DX11_RENDERER
    #include "RendererDX11.h"
#else
    #include "RendererOGL.h"
#endif
现在,无论您在哪里使用渲染器,都将其称为
renderer
类型,在头文件中包括
RendererTypes.h
,在cpp文件中包括
renderer.h

每个渲染器实现应该位于不同的项目中。然后创建不同的构建配置,以使用您想要使用的渲染器实现进行编译。例如,您不希望在Linux配置中包含DirectX代码

在调试版本中,仍然可以进行虚拟函数调用,但在发布版本中,它们会被优化掉,因为您从未通过基类接口进行调用。它仅用于在编译时为渲染器类强制执行公共签名


虽然此方法确实需要一点预处理器,但它是最小的,并且不会影响代码的可读性,因为它是隔离的,并且仅限于某些typedef和include。一个缺点是,您无法在运行时使用此方法切换渲染器实现,因为每个实现都将构建为单独的可执行文件。但是,在运行时确实不太需要切换配置。

我在应用程序中对渲染设备使用抽象基类的方法。工作正常,允许我在运行时动态选择要使用的渲染器。(如果不支持DirectX10,即在Windows XP上,我使用它从DirectX10切换到DirectX9)

我想指出的是,虚拟函数调用不是影响性能的部分,而是涉及的参数类型的转换。为了真正通用,渲染器的公共接口使用自己的一组参数类型,例如自定义IShader和自定义Matrix3D类型。DirectX API中声明的任何类型对应用程序的其余部分都不可见,因为OpenGL将具有不同的矩阵类型和着色器接口。这样做的缺点是,我必须将所有矩阵和向量/点类型从自定义类型转换为着色器在具体渲染设备实现中使用的类型。这比虚拟函数调用的成本要昂贵得多

如果使用预处理器进行区分,还需要像这样映射不同的接口类型。DirectX10和DirectX11之间有许多相同之处,但DirectX和OpenGL之间没有相同之处


编辑:有关示例实现,请参见中的答案

所以,我意识到这是一个老问题,但我忍不住插了进来。想要编写这样的代码只是试图处理面向对象灌输的副作用

第一个问题是,您是否真的需要交换渲染后端,或者只是认为它很酷。若可以在构建时为给定平台确定合适的后端,那个么问题就解决了:使用一个普通的、非虚拟的接口,并在构建时选择一个实现

如果您发现确实需要交换它,仍然使用非虚拟接口,只需将实现作为共享库加载即可。通过这种交换,您可能希望引擎渲染代码和一些性能密集型游戏特定渲染代码都被分解并可交换。通过这种方式,您可以使用通用的高级引擎渲染接口来处理主要由引擎完成的事情,同时仍然可以访问后端特定的代码,以避免PMF提到的转换成本

现在,应该说,在与共享库交换时引入了间接寻址,1。您可以很容易地将间接寻址设置为
初学者通常不会意识到这一点,因为现在有太多盲目的OO推动,但这种“OO优先,从不提问”的风格并非没有成本。这种设计有一个沉重的代码理解成本,并导致代码(比本例低得多的级别)本质上是缓慢的。当然,面向对象有它的位置,但是(在游戏和其他性能密集型应用程序中)我发现最好的设计方法是尽可能少地编写面向对象的应用程序,只有在遇到问题时才会让步。随着经验的积累,你会对如何划分界线有一种直觉。

你的解决方案更容易阅读,而预处理器和模板可以摆脱
虚拟的
函数。@dari确实如此。我喜欢可读性和简单性,但是