设计着色器类

时间:2011-08-28 07:35:53

标签: c++ qt opengl reference-counting

由于我已经开始学习OpenGL,我想我也应该编写一个小的C ++框架(对我来说),以避免过度使用C-ish代码显然导致的恶心。 :)因为我打算坚持使用Qt,所以框架使用了一些Qt类。

我真正需要的第一件事是使用着色器和程序的简单方法。这是我对着色器类的想法。

class Shader
{
public:
    //create a shader with no source code 
    explicit Shader(GLenum shaderType);
    //create a shader with source code and compile it
    Shader(GLenum shaderType, const QString& sourceCode);
    //create a shader from source file and compile it
    Shader(GLenum shaderType, QFile& sourceFile);
    ~Shader();

    //change the source code and recompile
    void Source(QFile& sourceFile);
    void Source(const QString& sourceCode);

    GLuint get() const; //get the handle

private:
    //common part for creation in different constructors
    void createShader(GLenum shaderType); 
    //compile
    void compile();

private:
    GLuint handle;
};

不同功能的作用必须非常明显。每个都调用相关的OpenGL例程,检查错误并在发生任何故障时抛出异常。构造函数调用glCreateShader。现在是棘手的部分。析构函数需要调用glDeleteShader(handle);,但在这种情况下我遇到了两难:

选项1:禁用分配和复制。这有避免引用计数的好处,以及被迫使用shared_pointers将这些放入向量并通常传播的缺点。

选项2:启用引用计数。这具有明显的优势,即允许复制,因此存储在容器中(我将需要稍后将一系列着色器传递给程序)。缺点如下:

Shader s1(GL_VERTEX_SHADER, QFile("MyVertexShader.vp"));
Shader s2(s1);
s2.Source(QFile("MyOtherVertexShader.vp"));

如您所见,我通过s2更改了s1的源代码,因为它们共享相同的内部着色器句柄。说实话,我不认为这里有大问题。我写了这个类,所以我知道它的复制语义是这样的,我很好。问题是我不确定这种设计是否可以接受。所有这些都可以通过Option1 +共享指针来实现,唯一的区别是我不希望每次创建着色器时都有共享指针(不是出于性能原因 - 只是出于语法方便)。

Q1:请评论选项和可选的整个想法。 1
Q2:如果我选择选项2,我是否必须自己实施,或者在boost或Qt中有一个现成的类,我可以从中派生或拥有一个成员,我会得到一个免费参考计数?
问题3:您是否同意将Shader作为一个抽象类并拥有三个派生类VertexShaderFragmentShaderGeometryShader会有些过分?

1 如果您应该将我推荐给现有的C ++ OpenGL框架,这非常好(因为我实际上没有找到),但这应该是真的是一个旁注而不是我的问题的答案。另请注意,我在文档中的某个地方看到过QGLShader类,但在我的Qt版本中显然没有,我现在有理由避免升级。

更新

感谢您的回答。我最终决定通过删除源函数使我的着色器类不可变。着色器在创建时编译,并且没有非const成员函数。因此,简单的引用计数可以立即解决我的所有问题。

2 个答案:

答案 0 :(得分:3)

我说使用选项1:它可以做任何选项2可以(通过智能指针),而选项2使你支付间接费用,即使你不需要它。最重要的是,它可以说更容易编写。

类似地,我曾经考虑在包装C API时使用handle-body / PIMPL,以允许从函数返回对象(C句柄类型不能保证可复制,因此间接是必需的)。我决定反对它,因为std::unique_ptr<T>不可移动 - &gt;可移动转换(多为shared_ptr<T>使T可复制)。从那时起,我设计我的类以具有“最紧密的”移动/复制语义。

然而,在语法噪音方面你确实有一点意义!像Boost.Phoenix和lambdas这样的东西往往有所帮助。如果/当它们不是一个选项时,我会说写一个单独的 shared_shader或其他包装器(包装器包装器?)是有道理的,至少对于库级代码(我相信就是这里的情况)。我不知道有什么用来帮助编写转发函数的单调乏味。

对于着色器我也不太了解,所以我不确定我能回答你的最后一个问题。如果不同着色器的数量经常发生变化,我认为制作类层次结构会有意义。我不认为是这样的;我也认为即使是这种情况,因为你级别的实现是包装一个预先存在的API,如果/当添加一个新的着色器时重新访问转发到该API的代码并不是太麻烦。


因为你要的是一个凤凰城的好例子。

假设我不需要取消引用,我想做什么:

std::transform(begin, end, functor);

相反:

std::for_each(begin, end, *arg1 = ref(functor)(*arg1));

使用凤凰城的一些设施(std::transform IIRC)仍然可以使用construct(为清晰起见),但这需要分配费用。

答案 1 :(得分:3)

我已经评估了这些选项,并且我实现了一个着色器类是一种不同的方式。

第一点是CreateShader和DeleteShader需要一个当前的上下文,但并不总是如此。所有函数都返回错误,后者可能导致泄漏。因此,我将介绍一个真正调用CreateShader和Delete Shader的Create和Delete例程。通过这种方式,即使在单独的线程中也可以销毁对象(当上下文是最新的时,着色器本身将被销毁。

第二点是着色器对象一旦链接到着色器程序,就可以在另一个着色器程序中重新链接,而无需重新编译(除非源依赖于预处理程序符号)。所以,我会收集一些commoly used shader对象的集合,在程序创建过程中重复使用。

最后一点是ShaderObject类的分配很好,只要你不泄漏创建的着色器对象。在源代码的情况下,我认为有两个选项:源可以更改,着色器变为无效,或着色器变脏,确实需要编译。

因为可以为不同的着色器阶段编译着色器源,我建议避免使用Vertex,Fragment等......派生。 (可选)您可以使用默认值并在创建之前进行设置。当然,可以通过定义create方法来实现。

另一点是,一旦链接了程序,就不需要存在着色器对象。因此,结合常见的预编译着色器数据库,引用计数仅由着色器程序(尤其是未链接的程序)使用,以指示需要着色器对象进行链接。在这种情况下,应该有一个着色器程序数据库,以避免冗余的程序创建;在这种情况下,赋值和复制成为一种非常罕见的操作,我将避免暴露;相反,定义一个友元方法并在你的框架中使用它。

相关问题