设计着色器类

Arm*_*yan 5 c++ opengl qt 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;
};
Run Code Online (Sandbox Code Playgroud)

必须非常清楚不同的功能在做什么.每个都调用相关的OpenGL例程,检查错误并在发生任何故障时抛出异常.构造函数调用glCreateShader.现在是棘手的部分.析构函数需要调用,glDeleteShader(handle);但在这种情况下我有一个两难的境地:

选项1:禁用分配和复制.这具有避免引用计数的优点,以及被迫使用shared_pointers将这些放入向量和传递中的缺点.

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

Shader s1(GL_VERTEX_SHADER, QFile("MyVertexShader.vp"));
Shader s2(s1);
s2.Source(QFile("MyOtherVertexShader.vp"));
Run Code Online (Sandbox Code Playgroud)

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

Q1:请评论选项和可选的整个想法.1
Q2:如果我选择选项2,我是否必须自己实现它,或者我可以从boost或Qt获得一个现成的类,我可以从中获得一个免费的引用计数?
Q3:你是否同意让Shader一个抽象类,有三个派生类VertexShader,FragmentShader以及GeometryShader会矫枉过正?

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

UPDATE

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

Luc*_*ton 3

我说使用选项 1:它可以完成选项 2 可以做的所有事情(通过智能指针),而选项 2 让你支付间接成本,即使你不需要它。最重要的是,它可以说更容易编写。

同样,我曾经考虑过在包装 C API 时使用句柄主体/PIMPL,以允许从函数返回对象(C 句柄类型不能保证可复制,因此间接是必要的)。我决定反对它,因为std::unique_ptr<T>不可移动 -> 可移动转换(就像shared_ptr<T>T复制一样)。从那时起,我将我的类设计为具有“最严格”的移动/复制语义。

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

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


既然你要求的是凤凰城善良的例子。

假设我不必取消引用我想做的事情:

std::transform(begin, end, functor);
Run Code Online (Sandbox Code Playgroud)

反而:

std::for_each(begin, end, *arg1 = ref(functor)(*arg1));
Run Code Online (Sandbox Code Playgroud)

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