1

Parts of the OpenGL API feel very clumsy/awkward, especially setting up vertex structures.

Here is an example from JUCE that illustrates this: https://github.com/julianstorer/JUCE/blob/master/examples/Demo/Source/Demos/OpenGLDemo.cpp#L51

You can see that if you were to add a new shader with a different vertex structure, you would have to rewrite a lot of lines of very similar code.

It should be possible using C++11 constructs to create a generic component capable of handling arbitrary vertex structures, that eliminates all of this boilerplate.

But how to go about this? What would a sensible interface look like?

Many people will have gone about solving this problem in different ways, however I'm guessing there are only a small number of viable solution paths. Hence I don't think this question is too open...


My thoughts so far...

A vertex might be:

struct V {
    GLFloat position[3];
    GLInt somethingElse;
    GLFloat color[4];
}

i.e. Some number of attributes, where each attribute is an array (maybe length 1) of some GL primitive type (of which there are about a dozen -- floats doubles ints even matrices...).

It may be possible to use glGetActiveAttrib, which allows one to extract the attributes from the shader source: 1, 2

So the workflow for the consumer would be: inspect the vertex shader, examine the attributes, create a C-struct as above to mirror, and then use glGetActiveAttrib to hook up each attribute to its respective offset in the C-struct.

This sounds possibly dodgy, maybe there is some alignment issue?

Another approach might be something like this:

V = VertGen< 
            "position"      , GLFloat , 3 , 
            "somethingElse" , GLInt   , 1 ,
            "color"         , GLFloat , 4 
           >

C++ would need to somehow loop through these, using some kind of parameter pack car/cdr type recursion.

This actually looks pretty reasonable, it forces the consumer to layout clearly the attribute structure of the shader.

But then how can the consumer refer to a particular attribute?

myvert["position"] = ...; // ?

This would be an expensive dictionary look up; imagine we are filling out a mesh of thousands of vertices.

Is there such a thing as a compile-time dictionary lookup? That would be handy?

Otherwise we would be left with having to use:

myvert[0] = ...; // meh, heading towards unreadable code

// or...
enum { pos, se, color };
myvert[pos] = ...; // this seems reasonable

The only niggle here is that it's requiring the consumer to write things twice:

V = VertGen< 
            "position"      , GLFloat , 3 , 
            "somethingElse" , GLInt   , 1 ,
            "color"         , GLFloat , 4 
           >
enum { pos, se, color };

So is there any way round that?

Other GL C++ wrappers

Are there any plans or existing projects to port OpenGL API to C++?

^ another super-useful question closed :| why why why does the community do this? ROAR

Here are the candidates I've managed to find:

GL Toolkit
Unofficial OpenGL SDK
GL++
OOGL -- Object-oriented OpenGL
OGLplus

(Four years ago I wrote a basic GLES2 engine in ObjC https://github.com/p-i-/Enjinn -- it doesn't belong on this list but I will link it anyway).

Community
  • 1
  • 1
P i
  • 29,020
  • 36
  • 159
  • 267
  • Not a runtime solution, but I prefer _preprocessing_ solutions, that will automatically create _BaseClasses_ out of defined glsl programs. – t.niese Feb 27 '15 at 23:00
  • 1
    I would suggest you investigate oglplus, especially the examples in which the shapes namespace is used. – Jherico Feb 27 '15 at 23:35
  • @t.niese, now if only C++ had a decent preprocessor ;) – P i Feb 27 '15 at 23:44
  • @Jherico, thanks for oglplus -- however it doesn't support GLES2 (min is OpenGL3) which might rule it out for integration into JUCE (which targets iOS & Android) -- I don't know what fraction of the market share of mobile devices can run OpenGL3. – P i Feb 27 '15 at 23:45
  • OpenGL ES 3.0 should be supported on android devices post 4.3 and on any iOS devices with an A7 processor or higher. – Jherico Feb 28 '15 at 00:44

3 Answers3

2

UPDATE: Be careful when using this code for model storage or attribute binding. Models must be stroed with proper alignment, but my container have none. Ok, here is my implementation.

It's inline&template-only, so you should put it in a header.

Keep in mind that it expects that locations of vertex attribs are specified in a shader with layout(location = ..) and start with 0 (then 1,2,3...).


Features:

Attribute setup:

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:

Arrays of this type are useful for storing models.

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;`

Useful functions:

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


Code:
    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 *)&params...};
            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
  • 78,603
  • 9
  • 131
  • 207
  • This is super, thanks for sharing! I've been going through it today gradually figuring out what's going on. You could simplify a couple of your recursion terminators ([here](https://gist.github.com/p-i-/249e6ddd2594ece963ed)). – P i Feb 28 '15 at 23:55
  • I've been playing around with variations on your theme -- I've chalked up an answer below in case it is of any interest. – P i Mar 02 '15 at 18:43
1

I've been looking through HolyBlackCat's fantastic answer. I've done a partial rewrite, mainly to help me understand how it works.

I'm allowing a slightly more compact interface syntax:

using V = Attribute< float[3], int[1], double[4] >;

I also did some minor formatting to HBC's answer: github

coliru

// 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
    }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
P i
  • 29,020
  • 36
  • 159
  • 267
  • Nice idea with arrays instead of vectors. BTW, you should not use names like `_Tn`; see this: http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier . – HolyBlackCat Mar 03 '15 at 00:08
0

I am doing something similar, what you have already described, but I am tackling the issue from an other point of view: I define a struct for each asset type I am loading, since this is where the data comes from in the first place (so you'll have to create for each asset type [.obj, .xbf, ...] such a struct with metadata). Later, when it is decided with what shaders the object gets to be displayed the buffer generation and vao settings can be easily obtained from the struct (the metadata, which is a map from an attribute name to an offset).

Sure this is not the "super awesome beats everything" solution, but it reduces the generation of such structs the the number of file formats you want to support. Ant this number is (in my case at least) a lot smaller than the number of shader permutaions I am running.

hakononakani
  • 305
  • 2
  • 10