可变参数模板和顶点缓冲区属性
Variadic Templates and Vertex Buffer Attributes
在 OpenGL 中,创建 VBO 时,必须完成以下所有 3 件事:
- 创建缓冲区
unsigned int vboId;
glGenBuffers(1, &vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
- 确保缓冲区已绑定
glBindBuffer(GL_ARRAY_BUFFER, vboId);
- 启用并定义属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
在原始 OpenGL 中执行此操作虽然简单,但如果要为单个缓冲区设置许多属性,则可能会失控。我在想我可以使用带有 API 的可变模板来简化调用属性函数的过程,如下所示:
// 3 float position, 2 float uv texture coord.
float data[] {
0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 1.f, 0.f, 0.f, 1.f,
1.f, 0.f, 0.f, 1.f, 0.f,
1.f, 1.f, 0.f, 1.f, 1.f,
};
auto vboId = createVbo(data, sizeof(data));
bindVbo(vboId);
setVboLayout<float, 3, float, 2>(vboId);
但是,这是不可行的,因为模板参数 (AFAIK) 实际上不能可变,而只能使参数可变。之所以不想使用函数参数,是因为我希望能够输入C++关键字float
和int
,不能作为参数使用。我当前的解决方案是使用带有重复基本类型的枚举的参数,例如 Float32
和 Int32
。我想知道是否可以使用可变参数模板做这样的事情
您可以很好地制作可变模板参数,而无需从函数参数中扣除它们。但是,您不能声明您希望的那种交替类型和值参数 (<float, 3, float, 2>
)。
一种解决方案是使用复合类型来存储这两种信息,IMO 数组类型非常适合。所以你会声明并调用为:
template <class... Attributes>
void setVboLayout(VboId vboId);
setVboLayout<float[3], float[2]>(vboId);
... 这将分派到辅助模板,这些模板可以通过模板专业化匹配数组类型,或者使用 std::extent
检索大小。
示例实现:
namespace detail {
template <class Attribute>
struct attributeTag { };
std::size_t bindAttribute(VboId, attributeTag<float>, std::size_t offset) {
// glVertexAttribPointer for a float
return offset + sizeof(float);
}
template <std::size_t ArraySize>
std::size_t bindAttribute(VboId, attributeTag<float[ArraySize]>, std::size_t offset) {
// glVertexAttribPointer for a float array
return offset + sizeof(float[ArraySize]);
}
// More overloads for variouts attribute types...
}
template <class... Attributes>
void setVboLayout(VboId vboId) {
std::size_t offset = 0;
((offset = detail::bindAttribute(vboId, detail::attributeTag<Attributes>{}, offset)), ...);
}
此处 bindAttribute
重载每个 return 刚刚注册的属性之后的偏移量。
在 OpenGL 中,创建 VBO 时,必须完成以下所有 3 件事:
- 创建缓冲区
unsigned int vboId;
glGenBuffers(1, &vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
- 确保缓冲区已绑定
glBindBuffer(GL_ARRAY_BUFFER, vboId);
- 启用并定义属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
在原始 OpenGL 中执行此操作虽然简单,但如果要为单个缓冲区设置许多属性,则可能会失控。我在想我可以使用带有 API 的可变模板来简化调用属性函数的过程,如下所示:
// 3 float position, 2 float uv texture coord.
float data[] {
0.f, 0.f, 0.f, 0.f, 0.f,
0.f, 1.f, 0.f, 0.f, 1.f,
1.f, 0.f, 0.f, 1.f, 0.f,
1.f, 1.f, 0.f, 1.f, 1.f,
};
auto vboId = createVbo(data, sizeof(data));
bindVbo(vboId);
setVboLayout<float, 3, float, 2>(vboId);
但是,这是不可行的,因为模板参数 (AFAIK) 实际上不能可变,而只能使参数可变。之所以不想使用函数参数,是因为我希望能够输入C++关键字float
和int
,不能作为参数使用。我当前的解决方案是使用带有重复基本类型的枚举的参数,例如 Float32
和 Int32
。我想知道是否可以使用可变参数模板做这样的事情
您可以很好地制作可变模板参数,而无需从函数参数中扣除它们。但是,您不能声明您希望的那种交替类型和值参数 (<float, 3, float, 2>
)。
一种解决方案是使用复合类型来存储这两种信息,IMO 数组类型非常适合。所以你会声明并调用为:
template <class... Attributes>
void setVboLayout(VboId vboId);
setVboLayout<float[3], float[2]>(vboId);
... 这将分派到辅助模板,这些模板可以通过模板专业化匹配数组类型,或者使用 std::extent
检索大小。
示例实现:
namespace detail {
template <class Attribute>
struct attributeTag { };
std::size_t bindAttribute(VboId, attributeTag<float>, std::size_t offset) {
// glVertexAttribPointer for a float
return offset + sizeof(float);
}
template <std::size_t ArraySize>
std::size_t bindAttribute(VboId, attributeTag<float[ArraySize]>, std::size_t offset) {
// glVertexAttribPointer for a float array
return offset + sizeof(float[ArraySize]);
}
// More overloads for variouts attribute types...
}
template <class... Attributes>
void setVboLayout(VboId vboId) {
std::size_t offset = 0;
((offset = detail::bindAttribute(vboId, detail::attributeTag<Attributes>{}, offset)), ...);
}
此处 bindAttribute
重载每个 return 刚刚注册的属性之后的偏移量。