用于管理 OpenGL 顶点属性的 C++ 接口
C++ interface for managing OpenGL vertex attributes
部分 OpenGL API 感觉非常 clumsy/awkward,尤其是设置顶点结构。
这是 JUCE 的一个例子,说明了这一点:https://github.com/julianstorer/JUCE/blob/master/examples/Demo/Source/Demos/OpenGLDemo.cpp#L51
您可以看到,如果要添加一个具有不同顶点结构的新着色器,您将不得不重写很多行非常相似的代码。
应该可以使用 C++11 构造来创建能够处理任意顶点结构的通用组件,从而消除所有这些样板文件。
但是怎么办呢?合理的界面应该是什么样的?
许多人会以不同的方式解决这个问题,但我猜只有少数可行的解决方案路径。因此,我认为这个问题不太开放...
到目前为止我的想法...
顶点可能是:
struct V {
GLFloat position[3];
GLInt somethingElse;
GLFloat color[4];
}
即一些属性,其中每个属性都是一些 GL 原始类型的数组(长度可能为 1)(其中大约有十几个——浮点数加倍整数偶数矩阵...)。
也许可以使用 glGetActiveAttrib,它允许从着色器源中提取属性:1, 2
所以消费者的工作流程是:检查顶点着色器,检查属性,创建一个 C-struct 如上所述进行镜像,然后使用 glGetActiveAttrib 将每个属性连接到它在 C-结构。
这听起来可能很狡猾,也许存在对齐问题?
另一种方法可能是这样的:
V = VertGen<
"position" , GLFloat , 3 ,
"somethingElse" , GLInt , 1 ,
"color" , GLFloat , 4
>
C++ 需要以某种方式循环遍历这些,使用某种参数包 car/cdr 类型递归。
这个其实看起来挺合理的,逼着消费者把shader的属性结构布局清楚。
但是消费者如何引用特定属性?
myvert["position"] = ...; // ?
这将是一个昂贵的字典查找;假设我们正在填充一个包含数千个顶点的网格。
是否有编译时字典查找之类的东西?那会很方便吗?
否则我们将不得不使用:
myvert[0] = ...; // meh, heading towards unreadable code
// or...
enum { pos, se, color };
myvert[pos] = ...; // this seems reasonable
这里唯一的问题是它要求消费者写两次东西:
V = VertGen<
"position" , GLFloat , 3 ,
"somethingElse" , GLInt , 1 ,
"color" , GLFloat , 4
>
enum { pos, se, color };
那有什么办法吗?
其他 GL C++ 包装器
Are there any plans or existing projects to port OpenGL API to C++?
^ 另一个超级有用的问题已关闭:|为什么社区为什么要这样做? 怒吼
以下是我设法找到的候选人:
GL Toolkit
Unofficial OpenGL SDK
GL++
OOGL -- Object-oriented OpenGL
OGLplus
(四年前我用 ObjC https://github.com/p-i-/Enjinn 写了一个基本的 GLES2 引擎——它不属于这个列表,但我会 link 无论如何)。
我正在做类似的事情,您已经描述过,但我是从另一个角度解决这个问题:我为我正在加载的每种资产类型定义一个结构,因为这是数据的来源首先(因此您必须为每种资产类型 [.obj, .xbf, ...] 创建这样一个带有元数据的结构)。稍后,当决定用什么着色器显示对象时,可以很容易地从结构中获得缓冲区生成和 vao 设置(元数据,它是从属性名称到偏移量的映射)。
当然这不是 "super awesome beats everything" 解决方案,但它减少了此类结构的生成以及您要支持的文件格式的数量。蚂蚁这个数字(至少在我的情况下)比我 运行.
的着色器排列数小很多
更新:将此代码用于模型存储或属性绑定时要小心。模型必须正确对齐,但我的容器有 none。
好的,这是我的实现。
它是内联的&template-only,所以你应该把它放在header。
请记住,它期望顶点属性的位置在着色器中用 layout(location = ..)
指定并从 0 开始(然后是 1,2,3...)。
特点:
属性设置:
SetVertexAttribs<AttribLayout<Vec<float, 4>, Vec<int, 2>, vec<double, 3>>>();
// It enables attrib arrays (here: 0, 1 and 2), and then sets attrib pointers for them.
// It also selects correct glVertexAttrib{|I|L}Pointer function depending on type of each parametr.
Class Vec:
Vec<float, 4> a {1, 2, 3, 4}, b;
// First parametr can be any type. Second parametr is
// number of objects of that type in the vector.
// First parametr can be any default scalar type.
// Second parametr can only be 1, 2, 3 or 4.
a.x = 2; // you can use .x or .r - they have same meaning
a.y = 3; // you can use .y or .g - they have same meaning
a.z = 4; // you can use .z or .b - they have same meaning
a.w = 4; // you can use .w or .a - they have same meaning
a = a + a - a + -a; // Overloaded operators
a = {1, 2, 3, 4}; b = a; // Overloaded assignment
a += {0, 1, 2, 3}; b += a;
a -= {0, 1, 0, 1}; b -= a;
Vec<float, 4>::type var; // here means `float var;`
// Vec<...>::type means type of a single element
std::cout << a.dim; // here prints 4 -- number of dimensions of a vector
Class AttribLayout:
这种类型的数组对于存储模型很有用。
AttribLayout<Vec<float, 4>, Vec<int, 2>, Vec<double, 3>>
obj1, obj2({0,0,0,0},{1,2},{10,20,30}); // Constructors
// This class uses trick with char array - don't worry about padding, it does not have it.
std::cout << obj1.elements; // Here prints 3 -- number of vectors in layout
std::cout << obj1.byte_len; // Same as `sizeof obj1`
Vec<int, 2> var = obj1.get<1>(); // Returns vector with a specific number in a layout
double dvar = obj2.get<2,1>; // Here dvar == 20. First parametr means number of vector in a layout, second parametr means number of element in a vector.
obj1.set<1>({1,2}); // Parametrs mean same things as in `get()`.
obj1.set<2,0>(0.0); // I think you understood what `set()` does.
AttribLayout<Vec<float, 4>, Vec<int, 2>, vec<double, 3>>::type_at<1> var;
// Here means `Vec<int, 2> var;`
有用的功能:
type2glconst<int>::value // == GL_INT
// I think you understand what it's for.
type2glattribfunc<float> // same as glVertexAttribPointer
type2glattribfunc<double> // same as glVertexAttribLPointer
type2glattribfunc</*any integer type*/> // same as glVertexAttribIPointer
代码:
template <typename T>
struct type2glconst
{
static_assert(std::is_same<T, void>::value, "Invalid type!");
static constexpr GLenum value = 0;
};
template <> struct type2glconst<unsigned char> {static constexpr GLenum value = GL_UNSIGNED_BYTE;};
template <> struct type2glconst<signed char> {static constexpr GLenum value = GL_BYTE;};
template <> struct type2glconst<char> {static constexpr GLenum value = Utils::is_char_signed ? GL_BYTE : GL_UNSIGNED_BYTE;};
template <> struct type2glconst<unsigned short> {static constexpr GLenum value = GL_UNSIGNED_SHORT;};
template <> struct type2glconst<signed short> {static constexpr GLenum value = GL_SHORT;};
template <> struct type2glconst<unsigned int> {static constexpr GLenum value = GL_UNSIGNED_INT;};
template <> struct type2glconst<signed int> {static constexpr GLenum value = GL_INT;};
template <> struct type2glconst<float> {static constexpr GLenum value = GL_FLOAT;};
template <> struct type2glconst<double> {static constexpr GLenum value = GL_DOUBLE;};
template <typename T>
inline void type2glattribfunc(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer)
{
static_assert(type2glconst<T>::value, "Invalid type!");
glVertexAttribIPointer(index, size, type2glconst<T>::value, stride, pointer);
}
template <>
inline void type2glattribfunc<float>(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer)
{
glVertexAttribPointer(index, size, type2glconst<float>::value, GL_FALSE, stride, pointer);
}
template <>
inline void type2glattribfunc<double>(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer)
{
glVertexAttribLPointer(index, size, type2glconst<double>::value, stride, pointer);
}
template <typename T, unsigned int D>
struct Vec
{
static_assert((std::is_void<T>::value || type2glconst<T>::value) && D <= 4, "Invalid dimension for vector!");
static constexpr int dim = 0;
using type = void;
};
template <typename T>
struct Vec<T, 1>
{
using type = T;
static constexpr int dim = 1;
union {T x, r;};
Vec<T, 1> operator+(const Vec<T, 1> &o) const {return {x + o.x};}
Vec<T, 1> &operator+=(const Vec<T, 1> &o) {x += o.x; return *this;}
Vec<T, 1> operator-() const {return {-x};}
Vec<T, 1> operator-(const Vec<T, 1> &o) const {return {x - o.x};}
Vec<T, 1> &operator-=(const Vec<T, 1> &o) {x -= o.x; return *this;}
};
template <typename T>
struct Vec<T, 2>
{
using type = T;
static constexpr int dim = 2;
union {T x, r;};
union {T y, g;};
Vec<T, 2> operator+(const Vec<T, 2> &o) const {return {x + o.x, y + o.y};}
Vec<T, 2> &operator+=(const Vec<T, 2> &o) {x += o.x; y += o.y; return *this;}
Vec<T, 2> operator-() const {return {-x, -y};}
Vec<T, 2> operator-(const Vec<T, 2> &o) const {return {x - o.x, y - o.y};}
Vec<T, 2> &operator-=(const Vec<T, 2> &o) {x -= o.x; y -= o.y; return *this;}
};
template <typename T>
struct Vec<T, 3>
{
using type = T;
static constexpr int dim = 3;
union {T x, r;};
union {T y, g;};
union {T z, b;};
Vec<T, 3> operator+(const Vec<T, 3> &o) const {return {x + o.x, y + o.y, z + o.z};}
Vec<T, 3> &operator+=(const Vec<T, 3> &o) {x += o.x; y += o.y; z += o.z; return *this;}
Vec<T, 3> operator-() const {return {-x, -y, -z};}
Vec<T, 3> operator-(const Vec<T, 3> &o) const {return {x - o.x, y - o.y, z - o.z};}
Vec<T, 3> &operator-=(const Vec<T, 3> &o) {x -= o.x; y -= o.y; z -= o.z; return *this;}
Vec<T, 3> operator*(const Vec<T, 3> &o) const {return {y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x};}
Vec<T, 3> &operator*=(const Vec<T, 3> &o) {*this = *this * o; return *this;}
};
template <typename T>
struct Vec<T, 4>
{
using type = T;
static constexpr int dim = 4;
union {T x, r;};
union {T y, g;};
union {T z, b;};
union {T w, a;};
Vec<T, 4> operator+(const Vec<T, 4> &o) const {return {x + o.x, y + o.y, z + o.z, w + o.w};}
Vec<T, 4> &operator+=(const Vec<T, 4> &o) {x += o.x; y += o.y; z += o.z; w += o.w; return *this;}
Vec<T, 4> operator-() const {return {-x, -y, -z, -w};}
Vec<T, 4> operator-(const Vec<T, 4> &o) const {return {x - o.x, y - o.y, z - o.z, w - o.w};}
Vec<T, 4> &operator-=(const Vec<T, 4> &o) {x -= o.x; y -= o.y; z -= o.z; w -= o.w; return *this;}
};
template <typename T, typename ...P>
struct AttribLayout_InternalValue
{
static_assert(!std::is_same<T, void>::value, "Void vector is used in layout!");
using curtype = T;
};
template <typename T, typename ...P>
struct AttribLayout_InternalContainer
{
static_assert(!std::is_same<T, void>::value, "Void vector is used in layout!");
using curtype = T;
using nexttype = typename std::conditional<(sizeof...(P) > 1), AttribLayout_InternalContainer<P...>, AttribLayout_InternalValue<P...>>::type;
};
template <typename ...P>
class AttribLayout
{
protected:
static_assert(sizeof...(P) > 0, "Zero-length attrib layout!");
using cont_type = typename std::conditional<(sizeof...(P) > 1), AttribLayout_InternalContainer<P...>, AttribLayout_InternalValue<P...>>::type;
public:
static constexpr int elements = sizeof...(P);
protected:
template <unsigned int N, typename T> struct type_at_internal
{
using type = typename type_at_internal<(N - 1), typename T::nexttype>::type;
};
template <typename T> struct type_at_internal<0, T>
{
using type = T;
};
template <unsigned int N> using cont_type_at = typename type_at_internal<N, cont_type>::type;
public:
template <unsigned int N> using type_at = typename type_at_internal<N, cont_type>::type::curtype;
template <unsigned int N, typename T> struct bytes_internal
{
static constexpr unsigned int var = bytes_internal<(N - 1), T>::var + (type_at<(N - 1)>::dim * sizeof(typename type_at<(N - 1)>::type));
};
template <typename T> struct bytes_internal<0, T>
{
static constexpr unsigned int var = 0;
};
static constexpr unsigned int byte_len = bytes_internal<(sizeof...(P)), void>::var;
unsigned char bytearr[byte_len];
template <unsigned int N> type_at<N> get()
{
static_assert(N >= 0 && N < sizeof...(P), "Element is out of range!");
type_at<N> ret;
std::memcpy(&ret, bytearr + bytes_internal<N, void>::var, sizeof (type_at<N>));
return ret;
}
template <unsigned int N> void set(const type_at<N> &ref)
{
static_assert(N >= 0 && N < sizeof...(P), "Element is out of range!");
std::memcpy(bytearr + bytes_internal<N, void>::var, &ref, sizeof (type_at<N>));
}
template <unsigned int N, unsigned int X> typename type_at<N>::type get()
{
static_assert(N >= 0 && N < sizeof...(P), "Element is out of range!");
static_assert(X > 0 && X <= type_at<N>::dim, "Vector element is out of range!");
typename type_at<N>::type ret;
std::memcpy(&ret, bytearr + bytes_internal<N, void>::var + sizeof(typename type_at<N>::type) * (X - 1), sizeof (typename type_at<N>::type));
return ret;
}
template <unsigned int N, unsigned int X> void set(typename type_at<N>::type obj)
{
static_assert(N >= 0 && N < sizeof...(P), "Element is out of range!");
static_assert(X > 0 && X <= type_at<N>::dim, "Vector element is out of range!");
std::memcpy(bytearr + bytes_internal<N, void>::var + sizeof(typename type_at<N>::type) * (X - 1), &obj, sizeof (typename type_at<N>::type));
}
protected:
template <unsigned int N, unsigned int M> struct ctor_internal
{
static void func(void **ptr, unsigned char *arr)
{
std::memcpy(arr + bytes_internal<(M - N), void>::var, *ptr, sizeof (type_at<(M - N)>));
ctor_internal<(N - 1), M>::func(ptr + 1, arr);
}
};
template <unsigned int M> struct ctor_internal<0, M>
{
static void func(void **ptr, unsigned char *arr)
{
std::memcpy(arr + bytes_internal<M, void>::var, *ptr, sizeof (type_at<M>));
}
};
public:
AttribLayout()
{
static_assert(sizeof (decltype(*this)) == byte_len, "Crappy compiler have added padding to AttribLayout!");
static_assert(alignof (decltype(*this)) == 1, "Crappy compiler have added alignment to AttribLayout!");
}
AttribLayout(P ...params)
{
void *ptrs[sizeof...(P)] {(void *)¶ms...};
ctor_internal<(sizeof...(P) - 1), (sizeof...(P) - 1)>::func(ptrs, bytearr);
}
};
namespace InternalStuff
{
template <class T, unsigned N> struct SetVertexAttribs_internal
{
static void func()
{
SetVertexAttribs_internal<T, (N - 1)>::func();
glEnableVertexAttribArray(N);
type2glattribfunc<typename T::template type_at<N>::type>(N, T::template type_at<N>::dim, T::byte_len, (void *)T::template bytes_internal<N, void>::var);
}
};
template <class T> struct SetVertexAttribs_internal<T, 0>
{
static void func()
{
glEnableVertexAttribArray(0);
type2glattribfunc<typename T::template type_at<0>::type>(0, T::template type_at<0>::dim, T::byte_len, (void *)T::template bytes_internal<0, void>::var);
}
};
}
template <class T>
void SetVertexAttribs()
{
InternalStuff::SetVertexAttribs_internal<T, (T::elements - 1)>::func();
}
我一直在浏览 HolyBlackCat 的精彩回答。我做了部分重写,主要是为了帮助我理解它是如何工作的。
我允许使用稍微更紧凑的接口语法:
using V = Attribute< float[3], int[1], double[4] >;
我还对 HBC 的回答做了一些小的格式化:github
// https://github.com/julianstorer/JUCE/blob/master/examples/Demo/Source/Demos/OpenGLDemo.cpp#L51
#include <string>
#include <iostream>
#include <typeinfo>
#include <cstring> // memcpy (!)
#include <array>
#define COUT(x) cout << #x << ": " << x << endl
#define XCOUT(x) cout << #x << endl; x
using namespace std;
#define sc_uint static constexpr unsigned int
// from gltypes.h
typedef uint32_t GLbitfield;
typedef uint8_t GLboolean;
typedef int8_t GLbyte;
typedef float GLclampf;
typedef uint32_t GLenum;
typedef float GLfloat;
typedef int32_t GLint;
typedef int16_t GLshort;
typedef int32_t GLsizei;
typedef uint8_t GLubyte;
typedef uint32_t GLuint;
typedef uint16_t GLushort;
typedef void GLvoid;
// from gl3.h
#define GL_BYTE 0x1400
#define GL_UNSIGNED_BYTE 0x1401
#define GL_SHORT 0x1402
#define GL_UNSIGNED_SHORT 0x1403
#define GL_INT 0x1404
#define GL_UNSIGNED_INT 0x1405
#define GL_FLOAT 0x1406
#define GL_DOUBLE 0x140A
#define GL_FALSE 0
#define GL_TRUE 1
#define GLAPI
#define APIENTRY
GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer);
GLAPI void APIENTRY glVertexAttribIPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
GLAPI void APIENTRY glVertexAttribLPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
template <typename T>
struct glconst4type
{
static_assert( std::is_same<T, void>::value, "Invalid type!" );
static constexpr GLenum value = 0;
};
template <> struct glconst4type<unsigned char> {static constexpr GLenum value = GL_UNSIGNED_BYTE;};
template <> struct glconst4type<signed char> {static constexpr GLenum value = GL_BYTE;};
//template <> struct glconst4type<char> {static constexpr GLenum value = Utils::is_char_signed ? GL_BYTE : GL_UNSIGNED_BYTE;};
template <> struct glconst4type<unsigned short> {static constexpr GLenum value = GL_UNSIGNED_SHORT;};
template <> struct glconst4type<signed short> {static constexpr GLenum value = GL_SHORT;};
template <> struct glconst4type<unsigned int> {static constexpr GLenum value = GL_UNSIGNED_INT;};
template <> struct glconst4type<signed int> {static constexpr GLenum value = GL_INT;};
template <> struct glconst4type<float> {static constexpr GLenum value = GL_FLOAT;};
template <> struct glconst4type<double> {static constexpr GLenum value = GL_DOUBLE;};
template <typename T>
inline void generic_glVertexAttribPointer(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer) {
static_assert( glconst4type<T>::value, "Invalid type!" );
glVertexAttribIPointer( index, size, glconst4type<T>::value, stride, pointer );
}
template <>
inline void generic_glVertexAttribPointer<float>(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer) {
glVertexAttribPointer( index, size, glconst4type<float>::value, GL_FALSE, stride, pointer );
}
template <>
inline void generic_glVertexAttribPointer<double>(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer) {
glVertexAttribLPointer( index, size, glconst4type<double>::value, stride, pointer );
}
template <typename T, unsigned int D>
struct Vec {
static_assert( (std::is_void<T>::value || glconst4type<T>::value) && D <= 4, "Invalid dimension for vector!" );
static constexpr int dim = 0;
using type = void;
};
template <typename T>
struct Vec<T, 1> {
using type = T;
static constexpr int dim = 1;
T x;
Vec<T, 1> operator- () const {return {-x};}
Vec<T, 1> operator+ (const Vec<T, 1> &o) const {return {x + o.x};}
Vec<T, 1> operator- (const Vec<T, 1> &o) const {return {x - o.x};}
Vec<T, 1> &operator+=(const Vec<T, 1> &o) {x += o.x; return *this;}
Vec<T, 1> &operator-=(const Vec<T, 1> &o) {x -= o.x; return *this;}
};
template <typename T>
struct Vec<T, 2> {
using type = T;
static constexpr int dim = 2;
union {T x, u;};
union {T y, v;};
Vec<T, 2> operator- () const {return {-x, -y};}
Vec<T, 2> operator+ (const Vec<T, 2> &o) const {return {x + o.x, y + o.y};}
Vec<T, 2> operator- (const Vec<T, 2> &o) const {return {x - o.x, y - o.y};}
Vec<T, 2> &operator+=(const Vec<T, 2> &o) {x += o.x; y += o.y; return *this;}
Vec<T, 2> &operator-=(const Vec<T, 2> &o) {x -= o.x; y -= o.y; return *this;}
};
template <typename T>
struct Vec<T, 3> {
using type = T;
static constexpr int dim = 3;
union {T x, r;};
union {T y, g;};
union {T z, b;};
Vec<T, 3> operator- () const {return {-x, -y, -z};}
Vec<T, 3> operator+ (const Vec<T, 3> &o) const {return {x + o.x, y + o.y, z + o.z};}
Vec<T, 3> operator- (const Vec<T, 3> &o) const {return {x - o.x, y - o.y, z - o.z};}
Vec<T, 3> operator* (const Vec<T, 3> &o) const {return {y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x};}
Vec<T, 3> &operator+=(const Vec<T, 3> &o) {x += o.x; y += o.y; z += o.z; return *this;}
Vec<T, 3> &operator-=(const Vec<T, 3> &o) {x -= o.x; y -= o.y; z -= o.z; return *this;}
Vec<T, 3> &operator*=(const Vec<T, 3> &o) {*this = *this * o; return *this;}
};
template <typename T>
struct Vec<T, 4> {
using type = T;
static constexpr int dim = 4;
union {T x, r;};
union {T y, g;};
union {T z, b;};
union {T w, a;};
Vec<T, 4> operator- () const {return {-x, -y, -z, -w};}
Vec<T, 4> operator+ (const Vec<T, 4> &o) const {return {x + o.x, y + o.y, z + o.z, w + o.w};}
Vec<T, 4> operator- (const Vec<T, 4> &o) const {return {x - o.x, y - o.y, z - o.z, w - o.w};}
Vec<T, 4> &operator+=(const Vec<T, 4> &o) {x += o.x; y += o.y; z += o.z; w += o.w; return *this;}
Vec<T, 4> &operator-=(const Vec<T, 4> &o) {x -= o.x; y -= o.y; z -= o.z; w -= o.w; return *this;}
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
template < int k, int offset_, typename... P >
struct Recursor;
template < int k, int offset_, typename Tn_, typename... P >
struct Recursor<k, offset_, Tn_, P...>
{
public:
// split Tn into Type[dim]
using Tn = Tn_;
using EltType = typename remove_extent<Tn>::type;
sc_uint dim = extent<Tn>::value;
sc_uint offset = offset_;
sc_uint attr_size = sizeof(Tn);
sc_uint elt_size = sizeof(EltType);
private:
// remember at least one elt has been split off from P
static_assert( 0 <= k && k <= sizeof...(P), "k out of bounds" );
struct Terminator {
using destination = void;
sc_uint bytecount = 0;
static void setup_gl(int, int) { cout << "...setup_gl done!" << endl; }
};
using This = Recursor< k , offset , Tn, P... >;
using Next = typename conditional<
k == 0,
Terminator,
Recursor< k-1, offset+attr_size, P... >
>::type;
public:
// usage: e.g.
// using Ri = Recursor<i, 0, W>::destination;
// cout << Ri::offset;
using destination = typename conditional< k == 0, This, typename Next::destination >::type;
// bytecount counts bytes of elements k thru N, so bytecount of k=0
// will calculate total bytes
sc_uint bytecount = attr_size + Next::bytecount;
// MUST call this like:
// Recursor<N-1, 0, W>::setup_gl(0); // N = number of attributes
static void setup_gl(int total_bytes, int attribNumber)
{
//glEnableVertexAttribArray( attribNumber );
//generic_glVertexAttribPointer< EltType >(
// attribNumber,
// dim,
// size,
// offset
// );
cout << "k: " << k << ":"
<< dim
<< typeid(EltType).name() << "("
<< "attr_size:" << attr_size << ", "
<< "offset: " << offset << ")" << endl;
Next::setup_gl( total_bytes, attribNumber+1 );
}
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
template< typename... P >
struct Attribute
{
sc_uint N = sizeof...(P);
// Extract data for the k-th parameter in pack P
// (Always pass an initial set of 0)
template< int k >
using R = Recursor< k, 0, P... >;
// Raw storage
sc_uint total_bytes = R<N-1>::bytecount;
uint8_t data[ total_bytes ];
static void setup_gl() {
R<N-1>::setup_gl(total_bytes, 0);
}
template< int k >
using A = typename R<k>::destination;
template< int k > using Tn = typename A<k>::Tn; // of form T[n], e.g. float[3]
template< int k > using EltType = typename A<k>::EltType;
template <int k>
using V = Vec< typename A<k>::EltType, A<k>::dim >;
// ATTRIBUTE
// Get
template <int k>
V<k> get() {
static_assert( k>=0 && k<N, "Attribute index out of range!" );
V<k> ret;
memcpy( &ret, data + A<k>::offset, A<k>::attr_size );
return ret;
}
// Set
template <int k>
void set( void* src ) {
cout << "set<" << k << ">( void* )" << endl << endl;
static_assert( k>=0 && k<N, "Attribute index out of range!" );
memcpy( data + A<k>::offset, src, A<k>::attr_size );
}
template <int k> // need only this one!
void set( V<k> const& ref ) {
cout << "set<" << k << ">( V<" << k << "> const& ref )" << endl;
set<k>( (void*)&ref );
}
template <int k>
void set( Tn<k> const& ref ) {
cout << "set<" << k << ">( Tn<" << k << "> const& ref )" << endl;
set<k>( (void*)&ref );
}
// Attribute ELEMENT
// Get
template <int k, int e>
EltType<k> const get() {
static_assert( k>=0 && k<N && e>=0 && e < A<k>::dim, "Attribute or Element index out of range!" );
EltType<k> ret;
memcpy( &ret, data + A<k>::offset + e*A<k>::elt_size, A<k>::elt_size );
return ret;
}
// Set
template <int k, int e>
void set( const EltType<k>& ref ) {
static_assert( k>=0 && k<N && e>=0 && e < A<k>::dim, "Attribute or Element index out of range!" );
memcpy( data + A<k>::offset + e*A<k>::elt_size, &ref, A<k>::elt_size );
}
template< int k, typename P0, typename... Px >
void set_all( P0 const& p0, Px const& ... p )
{
set<k>( p0 );
set_all<k+1>( p... );
}
template< int index >
void set_all()
{ }
Attribute() {}
Attribute( P const& ... params ) {
set_all<0>( params... );
}
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int main()
{
if((1)){
using V = Attribute< int[2] >;
COUT( sizeof(V) );
COUT( V::total_bytes );
V::setup_gl();
{
V v;
int a0[2]{1,2};
v.set<0>( a0 );
COUT(( v.get<0,1>() ));
v.set<0>( {3,4} );
v.set<0>( a0 );
}
{
cout << "test vw" << endl;
V v( {1,2} );
V w( {{1,2}} );
auto a0 = v.get<0>();
a0.u += 10;
COUT(a0.u);
v.set<0>( a0 );
v.set<0>( {7,8} );
v.set<0,1>( 9 ); COUT(( v.get<0,1>() ));
v.set<0,1>( {10} ); COUT(( v.get<0,1>() ));
}
}
if((1)){
cout << "using V = Attribute< float[3], int[1], double[4] >;" << endl;
using V = Attribute< float[3], int[1], double[4] >;
COUT( V::A<0>::dim );
COUT( V::A<1>::dim );
COUT( V::A<2>::dim );
V v( {1.f,2.f,3.f}, {4}, {5.,6.,7.,8.} ); COUT(( v.get<2,2>() )); // 7
v.set<2>( { 50., 60., 70., 80. } ); COUT(( v.get<2,2>() )); // 70
v.set<2,2>( 100 ); COUT(( v.get<2,2>() )); // 100
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
部分 OpenGL API 感觉非常 clumsy/awkward,尤其是设置顶点结构。
这是 JUCE 的一个例子,说明了这一点:https://github.com/julianstorer/JUCE/blob/master/examples/Demo/Source/Demos/OpenGLDemo.cpp#L51
您可以看到,如果要添加一个具有不同顶点结构的新着色器,您将不得不重写很多行非常相似的代码。
应该可以使用 C++11 构造来创建能够处理任意顶点结构的通用组件,从而消除所有这些样板文件。
但是怎么办呢?合理的界面应该是什么样的?
许多人会以不同的方式解决这个问题,但我猜只有少数可行的解决方案路径。因此,我认为这个问题不太开放...
到目前为止我的想法...
顶点可能是:
struct V {
GLFloat position[3];
GLInt somethingElse;
GLFloat color[4];
}
即一些属性,其中每个属性都是一些 GL 原始类型的数组(长度可能为 1)(其中大约有十几个——浮点数加倍整数偶数矩阵...)。
也许可以使用 glGetActiveAttrib,它允许从着色器源中提取属性:1, 2
所以消费者的工作流程是:检查顶点着色器,检查属性,创建一个 C-struct 如上所述进行镜像,然后使用 glGetActiveAttrib 将每个属性连接到它在 C-结构。
这听起来可能很狡猾,也许存在对齐问题?
另一种方法可能是这样的:
V = VertGen<
"position" , GLFloat , 3 ,
"somethingElse" , GLInt , 1 ,
"color" , GLFloat , 4
>
C++ 需要以某种方式循环遍历这些,使用某种参数包 car/cdr 类型递归。
这个其实看起来挺合理的,逼着消费者把shader的属性结构布局清楚。
但是消费者如何引用特定属性?
myvert["position"] = ...; // ?
这将是一个昂贵的字典查找;假设我们正在填充一个包含数千个顶点的网格。
是否有编译时字典查找之类的东西?那会很方便吗?
否则我们将不得不使用:
myvert[0] = ...; // meh, heading towards unreadable code
// or...
enum { pos, se, color };
myvert[pos] = ...; // this seems reasonable
这里唯一的问题是它要求消费者写两次东西:
V = VertGen<
"position" , GLFloat , 3 ,
"somethingElse" , GLInt , 1 ,
"color" , GLFloat , 4
>
enum { pos, se, color };
那有什么办法吗?
其他 GL C++ 包装器
Are there any plans or existing projects to port OpenGL API to C++?
^ 另一个超级有用的问题已关闭:|为什么社区为什么要这样做? 怒吼
以下是我设法找到的候选人:
GL Toolkit
Unofficial OpenGL SDK
GL++
OOGL -- Object-oriented OpenGL
OGLplus
(四年前我用 ObjC https://github.com/p-i-/Enjinn 写了一个基本的 GLES2 引擎——它不属于这个列表,但我会 link 无论如何)。
我正在做类似的事情,您已经描述过,但我是从另一个角度解决这个问题:我为我正在加载的每种资产类型定义一个结构,因为这是数据的来源首先(因此您必须为每种资产类型 [.obj, .xbf, ...] 创建这样一个带有元数据的结构)。稍后,当决定用什么着色器显示对象时,可以很容易地从结构中获得缓冲区生成和 vao 设置(元数据,它是从属性名称到偏移量的映射)。
当然这不是 "super awesome beats everything" 解决方案,但它减少了此类结构的生成以及您要支持的文件格式的数量。蚂蚁这个数字(至少在我的情况下)比我 运行.
的着色器排列数小很多更新:将此代码用于模型存储或属性绑定时要小心。模型必须正确对齐,但我的容器有 none。 好的,这是我的实现。
它是内联的&template-only,所以你应该把它放在header。
请记住,它期望顶点属性的位置在着色器中用 layout(location = ..)
指定并从 0 开始(然后是 1,2,3...)。
特点:
属性设置:
SetVertexAttribs<AttribLayout<Vec<float, 4>, Vec<int, 2>, vec<double, 3>>>();
// It enables attrib arrays (here: 0, 1 and 2), and then sets attrib pointers for them.
// It also selects correct glVertexAttrib{|I|L}Pointer function depending on type of each parametr.
Class Vec:
Vec<float, 4> a {1, 2, 3, 4}, b;
// First parametr can be any type. Second parametr is
// number of objects of that type in the vector.
// First parametr can be any default scalar type.
// Second parametr can only be 1, 2, 3 or 4.
a.x = 2; // you can use .x or .r - they have same meaning
a.y = 3; // you can use .y or .g - they have same meaning
a.z = 4; // you can use .z or .b - they have same meaning
a.w = 4; // you can use .w or .a - they have same meaning
a = a + a - a + -a; // Overloaded operators
a = {1, 2, 3, 4}; b = a; // Overloaded assignment
a += {0, 1, 2, 3}; b += a;
a -= {0, 1, 0, 1}; b -= a;
Vec<float, 4>::type var; // here means `float var;`
// Vec<...>::type means type of a single element
std::cout << a.dim; // here prints 4 -- number of dimensions of a vector
Class AttribLayout:
这种类型的数组对于存储模型很有用。
AttribLayout<Vec<float, 4>, Vec<int, 2>, Vec<double, 3>>
obj1, obj2({0,0,0,0},{1,2},{10,20,30}); // Constructors
// This class uses trick with char array - don't worry about padding, it does not have it.
std::cout << obj1.elements; // Here prints 3 -- number of vectors in layout
std::cout << obj1.byte_len; // Same as `sizeof obj1`
Vec<int, 2> var = obj1.get<1>(); // Returns vector with a specific number in a layout
double dvar = obj2.get<2,1>; // Here dvar == 20. First parametr means number of vector in a layout, second parametr means number of element in a vector.
obj1.set<1>({1,2}); // Parametrs mean same things as in `get()`.
obj1.set<2,0>(0.0); // I think you understood what `set()` does.
AttribLayout<Vec<float, 4>, Vec<int, 2>, vec<double, 3>>::type_at<1> var;
// Here means `Vec<int, 2> var;`
有用的功能:
type2glconst<int>::value // == GL_INT
// I think you understand what it's for.
type2glattribfunc<float> // same as glVertexAttribPointer
type2glattribfunc<double> // same as glVertexAttribLPointer
type2glattribfunc</*any integer type*/> // same as glVertexAttribIPointer
代码:
template <typename T>
struct type2glconst
{
static_assert(std::is_same<T, void>::value, "Invalid type!");
static constexpr GLenum value = 0;
};
template <> struct type2glconst<unsigned char> {static constexpr GLenum value = GL_UNSIGNED_BYTE;};
template <> struct type2glconst<signed char> {static constexpr GLenum value = GL_BYTE;};
template <> struct type2glconst<char> {static constexpr GLenum value = Utils::is_char_signed ? GL_BYTE : GL_UNSIGNED_BYTE;};
template <> struct type2glconst<unsigned short> {static constexpr GLenum value = GL_UNSIGNED_SHORT;};
template <> struct type2glconst<signed short> {static constexpr GLenum value = GL_SHORT;};
template <> struct type2glconst<unsigned int> {static constexpr GLenum value = GL_UNSIGNED_INT;};
template <> struct type2glconst<signed int> {static constexpr GLenum value = GL_INT;};
template <> struct type2glconst<float> {static constexpr GLenum value = GL_FLOAT;};
template <> struct type2glconst<double> {static constexpr GLenum value = GL_DOUBLE;};
template <typename T>
inline void type2glattribfunc(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer)
{
static_assert(type2glconst<T>::value, "Invalid type!");
glVertexAttribIPointer(index, size, type2glconst<T>::value, stride, pointer);
}
template <>
inline void type2glattribfunc<float>(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer)
{
glVertexAttribPointer(index, size, type2glconst<float>::value, GL_FALSE, stride, pointer);
}
template <>
inline void type2glattribfunc<double>(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer)
{
glVertexAttribLPointer(index, size, type2glconst<double>::value, stride, pointer);
}
template <typename T, unsigned int D>
struct Vec
{
static_assert((std::is_void<T>::value || type2glconst<T>::value) && D <= 4, "Invalid dimension for vector!");
static constexpr int dim = 0;
using type = void;
};
template <typename T>
struct Vec<T, 1>
{
using type = T;
static constexpr int dim = 1;
union {T x, r;};
Vec<T, 1> operator+(const Vec<T, 1> &o) const {return {x + o.x};}
Vec<T, 1> &operator+=(const Vec<T, 1> &o) {x += o.x; return *this;}
Vec<T, 1> operator-() const {return {-x};}
Vec<T, 1> operator-(const Vec<T, 1> &o) const {return {x - o.x};}
Vec<T, 1> &operator-=(const Vec<T, 1> &o) {x -= o.x; return *this;}
};
template <typename T>
struct Vec<T, 2>
{
using type = T;
static constexpr int dim = 2;
union {T x, r;};
union {T y, g;};
Vec<T, 2> operator+(const Vec<T, 2> &o) const {return {x + o.x, y + o.y};}
Vec<T, 2> &operator+=(const Vec<T, 2> &o) {x += o.x; y += o.y; return *this;}
Vec<T, 2> operator-() const {return {-x, -y};}
Vec<T, 2> operator-(const Vec<T, 2> &o) const {return {x - o.x, y - o.y};}
Vec<T, 2> &operator-=(const Vec<T, 2> &o) {x -= o.x; y -= o.y; return *this;}
};
template <typename T>
struct Vec<T, 3>
{
using type = T;
static constexpr int dim = 3;
union {T x, r;};
union {T y, g;};
union {T z, b;};
Vec<T, 3> operator+(const Vec<T, 3> &o) const {return {x + o.x, y + o.y, z + o.z};}
Vec<T, 3> &operator+=(const Vec<T, 3> &o) {x += o.x; y += o.y; z += o.z; return *this;}
Vec<T, 3> operator-() const {return {-x, -y, -z};}
Vec<T, 3> operator-(const Vec<T, 3> &o) const {return {x - o.x, y - o.y, z - o.z};}
Vec<T, 3> &operator-=(const Vec<T, 3> &o) {x -= o.x; y -= o.y; z -= o.z; return *this;}
Vec<T, 3> operator*(const Vec<T, 3> &o) const {return {y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x};}
Vec<T, 3> &operator*=(const Vec<T, 3> &o) {*this = *this * o; return *this;}
};
template <typename T>
struct Vec<T, 4>
{
using type = T;
static constexpr int dim = 4;
union {T x, r;};
union {T y, g;};
union {T z, b;};
union {T w, a;};
Vec<T, 4> operator+(const Vec<T, 4> &o) const {return {x + o.x, y + o.y, z + o.z, w + o.w};}
Vec<T, 4> &operator+=(const Vec<T, 4> &o) {x += o.x; y += o.y; z += o.z; w += o.w; return *this;}
Vec<T, 4> operator-() const {return {-x, -y, -z, -w};}
Vec<T, 4> operator-(const Vec<T, 4> &o) const {return {x - o.x, y - o.y, z - o.z, w - o.w};}
Vec<T, 4> &operator-=(const Vec<T, 4> &o) {x -= o.x; y -= o.y; z -= o.z; w -= o.w; return *this;}
};
template <typename T, typename ...P>
struct AttribLayout_InternalValue
{
static_assert(!std::is_same<T, void>::value, "Void vector is used in layout!");
using curtype = T;
};
template <typename T, typename ...P>
struct AttribLayout_InternalContainer
{
static_assert(!std::is_same<T, void>::value, "Void vector is used in layout!");
using curtype = T;
using nexttype = typename std::conditional<(sizeof...(P) > 1), AttribLayout_InternalContainer<P...>, AttribLayout_InternalValue<P...>>::type;
};
template <typename ...P>
class AttribLayout
{
protected:
static_assert(sizeof...(P) > 0, "Zero-length attrib layout!");
using cont_type = typename std::conditional<(sizeof...(P) > 1), AttribLayout_InternalContainer<P...>, AttribLayout_InternalValue<P...>>::type;
public:
static constexpr int elements = sizeof...(P);
protected:
template <unsigned int N, typename T> struct type_at_internal
{
using type = typename type_at_internal<(N - 1), typename T::nexttype>::type;
};
template <typename T> struct type_at_internal<0, T>
{
using type = T;
};
template <unsigned int N> using cont_type_at = typename type_at_internal<N, cont_type>::type;
public:
template <unsigned int N> using type_at = typename type_at_internal<N, cont_type>::type::curtype;
template <unsigned int N, typename T> struct bytes_internal
{
static constexpr unsigned int var = bytes_internal<(N - 1), T>::var + (type_at<(N - 1)>::dim * sizeof(typename type_at<(N - 1)>::type));
};
template <typename T> struct bytes_internal<0, T>
{
static constexpr unsigned int var = 0;
};
static constexpr unsigned int byte_len = bytes_internal<(sizeof...(P)), void>::var;
unsigned char bytearr[byte_len];
template <unsigned int N> type_at<N> get()
{
static_assert(N >= 0 && N < sizeof...(P), "Element is out of range!");
type_at<N> ret;
std::memcpy(&ret, bytearr + bytes_internal<N, void>::var, sizeof (type_at<N>));
return ret;
}
template <unsigned int N> void set(const type_at<N> &ref)
{
static_assert(N >= 0 && N < sizeof...(P), "Element is out of range!");
std::memcpy(bytearr + bytes_internal<N, void>::var, &ref, sizeof (type_at<N>));
}
template <unsigned int N, unsigned int X> typename type_at<N>::type get()
{
static_assert(N >= 0 && N < sizeof...(P), "Element is out of range!");
static_assert(X > 0 && X <= type_at<N>::dim, "Vector element is out of range!");
typename type_at<N>::type ret;
std::memcpy(&ret, bytearr + bytes_internal<N, void>::var + sizeof(typename type_at<N>::type) * (X - 1), sizeof (typename type_at<N>::type));
return ret;
}
template <unsigned int N, unsigned int X> void set(typename type_at<N>::type obj)
{
static_assert(N >= 0 && N < sizeof...(P), "Element is out of range!");
static_assert(X > 0 && X <= type_at<N>::dim, "Vector element is out of range!");
std::memcpy(bytearr + bytes_internal<N, void>::var + sizeof(typename type_at<N>::type) * (X - 1), &obj, sizeof (typename type_at<N>::type));
}
protected:
template <unsigned int N, unsigned int M> struct ctor_internal
{
static void func(void **ptr, unsigned char *arr)
{
std::memcpy(arr + bytes_internal<(M - N), void>::var, *ptr, sizeof (type_at<(M - N)>));
ctor_internal<(N - 1), M>::func(ptr + 1, arr);
}
};
template <unsigned int M> struct ctor_internal<0, M>
{
static void func(void **ptr, unsigned char *arr)
{
std::memcpy(arr + bytes_internal<M, void>::var, *ptr, sizeof (type_at<M>));
}
};
public:
AttribLayout()
{
static_assert(sizeof (decltype(*this)) == byte_len, "Crappy compiler have added padding to AttribLayout!");
static_assert(alignof (decltype(*this)) == 1, "Crappy compiler have added alignment to AttribLayout!");
}
AttribLayout(P ...params)
{
void *ptrs[sizeof...(P)] {(void *)¶ms...};
ctor_internal<(sizeof...(P) - 1), (sizeof...(P) - 1)>::func(ptrs, bytearr);
}
};
namespace InternalStuff
{
template <class T, unsigned N> struct SetVertexAttribs_internal
{
static void func()
{
SetVertexAttribs_internal<T, (N - 1)>::func();
glEnableVertexAttribArray(N);
type2glattribfunc<typename T::template type_at<N>::type>(N, T::template type_at<N>::dim, T::byte_len, (void *)T::template bytes_internal<N, void>::var);
}
};
template <class T> struct SetVertexAttribs_internal<T, 0>
{
static void func()
{
glEnableVertexAttribArray(0);
type2glattribfunc<typename T::template type_at<0>::type>(0, T::template type_at<0>::dim, T::byte_len, (void *)T::template bytes_internal<0, void>::var);
}
};
}
template <class T>
void SetVertexAttribs()
{
InternalStuff::SetVertexAttribs_internal<T, (T::elements - 1)>::func();
}
我一直在浏览 HolyBlackCat 的精彩回答。我做了部分重写,主要是为了帮助我理解它是如何工作的。
我允许使用稍微更紧凑的接口语法:
using V = Attribute< float[3], int[1], double[4] >;
我还对 HBC 的回答做了一些小的格式化:github
// https://github.com/julianstorer/JUCE/blob/master/examples/Demo/Source/Demos/OpenGLDemo.cpp#L51
#include <string>
#include <iostream>
#include <typeinfo>
#include <cstring> // memcpy (!)
#include <array>
#define COUT(x) cout << #x << ": " << x << endl
#define XCOUT(x) cout << #x << endl; x
using namespace std;
#define sc_uint static constexpr unsigned int
// from gltypes.h
typedef uint32_t GLbitfield;
typedef uint8_t GLboolean;
typedef int8_t GLbyte;
typedef float GLclampf;
typedef uint32_t GLenum;
typedef float GLfloat;
typedef int32_t GLint;
typedef int16_t GLshort;
typedef int32_t GLsizei;
typedef uint8_t GLubyte;
typedef uint32_t GLuint;
typedef uint16_t GLushort;
typedef void GLvoid;
// from gl3.h
#define GL_BYTE 0x1400
#define GL_UNSIGNED_BYTE 0x1401
#define GL_SHORT 0x1402
#define GL_UNSIGNED_SHORT 0x1403
#define GL_INT 0x1404
#define GL_UNSIGNED_INT 0x1405
#define GL_FLOAT 0x1406
#define GL_DOUBLE 0x140A
#define GL_FALSE 0
#define GL_TRUE 1
#define GLAPI
#define APIENTRY
GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer);
GLAPI void APIENTRY glVertexAttribIPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
GLAPI void APIENTRY glVertexAttribLPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
template <typename T>
struct glconst4type
{
static_assert( std::is_same<T, void>::value, "Invalid type!" );
static constexpr GLenum value = 0;
};
template <> struct glconst4type<unsigned char> {static constexpr GLenum value = GL_UNSIGNED_BYTE;};
template <> struct glconst4type<signed char> {static constexpr GLenum value = GL_BYTE;};
//template <> struct glconst4type<char> {static constexpr GLenum value = Utils::is_char_signed ? GL_BYTE : GL_UNSIGNED_BYTE;};
template <> struct glconst4type<unsigned short> {static constexpr GLenum value = GL_UNSIGNED_SHORT;};
template <> struct glconst4type<signed short> {static constexpr GLenum value = GL_SHORT;};
template <> struct glconst4type<unsigned int> {static constexpr GLenum value = GL_UNSIGNED_INT;};
template <> struct glconst4type<signed int> {static constexpr GLenum value = GL_INT;};
template <> struct glconst4type<float> {static constexpr GLenum value = GL_FLOAT;};
template <> struct glconst4type<double> {static constexpr GLenum value = GL_DOUBLE;};
template <typename T>
inline void generic_glVertexAttribPointer(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer) {
static_assert( glconst4type<T>::value, "Invalid type!" );
glVertexAttribIPointer( index, size, glconst4type<T>::value, stride, pointer );
}
template <>
inline void generic_glVertexAttribPointer<float>(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer) {
glVertexAttribPointer( index, size, glconst4type<float>::value, GL_FALSE, stride, pointer );
}
template <>
inline void generic_glVertexAttribPointer<double>(GLuint index, GLint size, GLsizei stride, const GLvoid *pointer) {
glVertexAttribLPointer( index, size, glconst4type<double>::value, stride, pointer );
}
template <typename T, unsigned int D>
struct Vec {
static_assert( (std::is_void<T>::value || glconst4type<T>::value) && D <= 4, "Invalid dimension for vector!" );
static constexpr int dim = 0;
using type = void;
};
template <typename T>
struct Vec<T, 1> {
using type = T;
static constexpr int dim = 1;
T x;
Vec<T, 1> operator- () const {return {-x};}
Vec<T, 1> operator+ (const Vec<T, 1> &o) const {return {x + o.x};}
Vec<T, 1> operator- (const Vec<T, 1> &o) const {return {x - o.x};}
Vec<T, 1> &operator+=(const Vec<T, 1> &o) {x += o.x; return *this;}
Vec<T, 1> &operator-=(const Vec<T, 1> &o) {x -= o.x; return *this;}
};
template <typename T>
struct Vec<T, 2> {
using type = T;
static constexpr int dim = 2;
union {T x, u;};
union {T y, v;};
Vec<T, 2> operator- () const {return {-x, -y};}
Vec<T, 2> operator+ (const Vec<T, 2> &o) const {return {x + o.x, y + o.y};}
Vec<T, 2> operator- (const Vec<T, 2> &o) const {return {x - o.x, y - o.y};}
Vec<T, 2> &operator+=(const Vec<T, 2> &o) {x += o.x; y += o.y; return *this;}
Vec<T, 2> &operator-=(const Vec<T, 2> &o) {x -= o.x; y -= o.y; return *this;}
};
template <typename T>
struct Vec<T, 3> {
using type = T;
static constexpr int dim = 3;
union {T x, r;};
union {T y, g;};
union {T z, b;};
Vec<T, 3> operator- () const {return {-x, -y, -z};}
Vec<T, 3> operator+ (const Vec<T, 3> &o) const {return {x + o.x, y + o.y, z + o.z};}
Vec<T, 3> operator- (const Vec<T, 3> &o) const {return {x - o.x, y - o.y, z - o.z};}
Vec<T, 3> operator* (const Vec<T, 3> &o) const {return {y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x};}
Vec<T, 3> &operator+=(const Vec<T, 3> &o) {x += o.x; y += o.y; z += o.z; return *this;}
Vec<T, 3> &operator-=(const Vec<T, 3> &o) {x -= o.x; y -= o.y; z -= o.z; return *this;}
Vec<T, 3> &operator*=(const Vec<T, 3> &o) {*this = *this * o; return *this;}
};
template <typename T>
struct Vec<T, 4> {
using type = T;
static constexpr int dim = 4;
union {T x, r;};
union {T y, g;};
union {T z, b;};
union {T w, a;};
Vec<T, 4> operator- () const {return {-x, -y, -z, -w};}
Vec<T, 4> operator+ (const Vec<T, 4> &o) const {return {x + o.x, y + o.y, z + o.z, w + o.w};}
Vec<T, 4> operator- (const Vec<T, 4> &o) const {return {x - o.x, y - o.y, z - o.z, w - o.w};}
Vec<T, 4> &operator+=(const Vec<T, 4> &o) {x += o.x; y += o.y; z += o.z; w += o.w; return *this;}
Vec<T, 4> &operator-=(const Vec<T, 4> &o) {x -= o.x; y -= o.y; z -= o.z; w -= o.w; return *this;}
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
template < int k, int offset_, typename... P >
struct Recursor;
template < int k, int offset_, typename Tn_, typename... P >
struct Recursor<k, offset_, Tn_, P...>
{
public:
// split Tn into Type[dim]
using Tn = Tn_;
using EltType = typename remove_extent<Tn>::type;
sc_uint dim = extent<Tn>::value;
sc_uint offset = offset_;
sc_uint attr_size = sizeof(Tn);
sc_uint elt_size = sizeof(EltType);
private:
// remember at least one elt has been split off from P
static_assert( 0 <= k && k <= sizeof...(P), "k out of bounds" );
struct Terminator {
using destination = void;
sc_uint bytecount = 0;
static void setup_gl(int, int) { cout << "...setup_gl done!" << endl; }
};
using This = Recursor< k , offset , Tn, P... >;
using Next = typename conditional<
k == 0,
Terminator,
Recursor< k-1, offset+attr_size, P... >
>::type;
public:
// usage: e.g.
// using Ri = Recursor<i, 0, W>::destination;
// cout << Ri::offset;
using destination = typename conditional< k == 0, This, typename Next::destination >::type;
// bytecount counts bytes of elements k thru N, so bytecount of k=0
// will calculate total bytes
sc_uint bytecount = attr_size + Next::bytecount;
// MUST call this like:
// Recursor<N-1, 0, W>::setup_gl(0); // N = number of attributes
static void setup_gl(int total_bytes, int attribNumber)
{
//glEnableVertexAttribArray( attribNumber );
//generic_glVertexAttribPointer< EltType >(
// attribNumber,
// dim,
// size,
// offset
// );
cout << "k: " << k << ":"
<< dim
<< typeid(EltType).name() << "("
<< "attr_size:" << attr_size << ", "
<< "offset: " << offset << ")" << endl;
Next::setup_gl( total_bytes, attribNumber+1 );
}
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
template< typename... P >
struct Attribute
{
sc_uint N = sizeof...(P);
// Extract data for the k-th parameter in pack P
// (Always pass an initial set of 0)
template< int k >
using R = Recursor< k, 0, P... >;
// Raw storage
sc_uint total_bytes = R<N-1>::bytecount;
uint8_t data[ total_bytes ];
static void setup_gl() {
R<N-1>::setup_gl(total_bytes, 0);
}
template< int k >
using A = typename R<k>::destination;
template< int k > using Tn = typename A<k>::Tn; // of form T[n], e.g. float[3]
template< int k > using EltType = typename A<k>::EltType;
template <int k>
using V = Vec< typename A<k>::EltType, A<k>::dim >;
// ATTRIBUTE
// Get
template <int k>
V<k> get() {
static_assert( k>=0 && k<N, "Attribute index out of range!" );
V<k> ret;
memcpy( &ret, data + A<k>::offset, A<k>::attr_size );
return ret;
}
// Set
template <int k>
void set( void* src ) {
cout << "set<" << k << ">( void* )" << endl << endl;
static_assert( k>=0 && k<N, "Attribute index out of range!" );
memcpy( data + A<k>::offset, src, A<k>::attr_size );
}
template <int k> // need only this one!
void set( V<k> const& ref ) {
cout << "set<" << k << ">( V<" << k << "> const& ref )" << endl;
set<k>( (void*)&ref );
}
template <int k>
void set( Tn<k> const& ref ) {
cout << "set<" << k << ">( Tn<" << k << "> const& ref )" << endl;
set<k>( (void*)&ref );
}
// Attribute ELEMENT
// Get
template <int k, int e>
EltType<k> const get() {
static_assert( k>=0 && k<N && e>=0 && e < A<k>::dim, "Attribute or Element index out of range!" );
EltType<k> ret;
memcpy( &ret, data + A<k>::offset + e*A<k>::elt_size, A<k>::elt_size );
return ret;
}
// Set
template <int k, int e>
void set( const EltType<k>& ref ) {
static_assert( k>=0 && k<N && e>=0 && e < A<k>::dim, "Attribute or Element index out of range!" );
memcpy( data + A<k>::offset + e*A<k>::elt_size, &ref, A<k>::elt_size );
}
template< int k, typename P0, typename... Px >
void set_all( P0 const& p0, Px const& ... p )
{
set<k>( p0 );
set_all<k+1>( p... );
}
template< int index >
void set_all()
{ }
Attribute() {}
Attribute( P const& ... params ) {
set_all<0>( params... );
}
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int main()
{
if((1)){
using V = Attribute< int[2] >;
COUT( sizeof(V) );
COUT( V::total_bytes );
V::setup_gl();
{
V v;
int a0[2]{1,2};
v.set<0>( a0 );
COUT(( v.get<0,1>() ));
v.set<0>( {3,4} );
v.set<0>( a0 );
}
{
cout << "test vw" << endl;
V v( {1,2} );
V w( {{1,2}} );
auto a0 = v.get<0>();
a0.u += 10;
COUT(a0.u);
v.set<0>( a0 );
v.set<0>( {7,8} );
v.set<0,1>( 9 ); COUT(( v.get<0,1>() ));
v.set<0,1>( {10} ); COUT(( v.get<0,1>() ));
}
}
if((1)){
cout << "using V = Attribute< float[3], int[1], double[4] >;" << endl;
using V = Attribute< float[3], int[1], double[4] >;
COUT( V::A<0>::dim );
COUT( V::A<1>::dim );
COUT( V::A<2>::dim );
V v( {1.f,2.f,3.f}, {4}, {5.,6.,7.,8.} ); COUT(( v.get<2,2>() )); // 7
v.set<2>( { 50., 60., 70., 80. } ); COUT(( v.get<2,2>() )); // 70
v.set<2,2>( 100 ); COUT(( v.get<2,2>() )); // 100
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -