VertexArrayObject.hh 16.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2011, Computer Graphics Group RWTH Aachen University         //
// All rights reserved.                                                       //
////////////////////////////////////////////////////////////////////////////////

#ifndef ACGL_OPENGL_OBJECTS_VERTEXARRAYOBJECT_HH
#define ACGL_OPENGL_OBJECTS_VERTEXARRAYOBJECT_HH

/**
 * A VertexArrayObject is a predefined combination of (multiple) attributes of
 * (multiple) ArrayBuffers and optionally one ElementArrayBuffer.
 *
 * It's only present in OpenGL 3.0 onwards. For older implementations (or
 * embedded systems) see VertexBufferObject which is a softwareimplementation
 * of the same idea).
 * Alternatively, there are the GL_APPLE_vertex_array_object and
 * GL_ARB_vertex_array_object extensions for OpenGL 2.1.
 * OES_vertex_array_object for OpenGL ES (e.g. iOS 4.0+)
 *
 * A VAO will cache the enabled vertex attributes (set with glEnableVertexAttribArray)
 * and vertex attribute pointer calls (glVertexAttribPointer).
 * Binding a VAO will restore that state (saving a lot of gl calls to do that
 * manually).
 */

#include <ACGL/ACGL.hh>
#include <ACGL/OpenGL/GL.hh>
#if (ACGL_OPENGL_VERSION >= 30)

#include <ACGL/Base/Macros.hh>
#include <ACGL/OpenGL/Tools.hh>

#include <ACGL/OpenGL/Objects/ArrayBuffer.hh>
#include <ACGL/OpenGL/Objects/ElementArrayBuffer.hh>
#include <ACGL/OpenGL/Objects/ShaderProgram.hh>

#include <vector>

namespace ACGL{
namespace OpenGL{

class VertexArrayObject
{
    ACGL_NOT_COPYABLE(VertexArrayObject)

    // ==================================================================================================== \/
    // ============================================================================================ STRUCTS \/
    // ==================================================================================================== \/
public:
    struct Attribute
    {
        SharedArrayBuffer arrayBuffer; // the ArrayBuffer to use
        int32_t           attributeID; // the attribute from that ArrayBuffer
        GLint             location;    // a location the in-attribute from a shader is bound to
        // more Attribute properties can be looked up in the ArrayBuffer (like the name)
    };

58
59
60
61
62
63
    // ===================================================================================================== \/
    // ============================================================================================ TYPEDEFS \/
    // ===================================================================================================== \/
public:
    typedef std::vector< Attribute > AttributeVec;

64
65
66
67
68
69
70
    // ========================================================================================================= \/
    // ============================================================================================ CONSTRUCTORS \/
    // ========================================================================================================= \/
public:
    VertexArrayObject()
    :   mObjectName(0),
        mpElementArrayBuffer(),
71
72
        mAttributes(),
        mMode(GL_TRIANGLES)
73
74
75
76
77
78
79
80
81
82
83
84
85
    {
        glGenVertexArrays(1, &mObjectName);
        if (openGLCriticalErrorOccured() ) {
            ACGL::Utils::error() << "could not generate vertex array object!" << std::endl;
        }
    }

    virtual ~VertexArrayObject(void)
    {
        // as always, OpenGL will ignore object name 0
        glDeleteVertexArrays(1, &mObjectName);
    }

86
87
88
89
90
91
    // ==================================================================================================== \/
    // ============================================================================================ SETTERS \/
    // ==================================================================================================== \/
public:
    inline void setMode(GLenum _mode) { mMode = _mode; }

92
93
94
95
    // ==================================================================================================== \/
    // ============================================================================================ GETTERS \/
    // ==================================================================================================== \/
public:
96
97
98
99
    inline       GLuint        operator()   (void) const { return mObjectName; }
    inline       GLuint        getObjectName(void) const { return mObjectName; }
    inline const AttributeVec& getAttributes(void) const { return mAttributes; }
    inline       GLenum        getMode      (void) const { return mMode;       }
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

    // ==================================================================================================== \/
    // ============================================================================================ METHODS \/
    // ==================================================================================================== \/
public:
    /**
     * Will check if the VAO looks ok (e.g. there is at least one ArrayBuffer and all ArrayBuffers
     * have the same number of elements).
     * A failed test will output an error but won't have other consequences.
     */
    bool isValid(void) const;

    /**
     * Set the given ElementArrayBuffer, if a NULL pointer is given, an existing EAB will get unset.
     * Will restore the previously bound VAO (DSA style)
     */
    void attachElementArrayBuffer( const SharedElementArrayBuffer& _elementArrayBuffer )
    {
118
        enable();
119
120
121

        mpElementArrayBuffer = _elementArrayBuffer;

122
123
        if (mpElementArrayBuffer) // could be set to NULL!
        {
124
            mpElementArrayBuffer->bind();
125
126
127
        }
        else
        {
128
129
130
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        }

131
        disable();
132
133
134
135
136
137
138
139
    }

    inline void detachElementArrayBuffer() { attachElementArrayBuffer( SharedElementArrayBuffer() ); }

    /**
     * Will set the attribute _arrayBufferAttribute of ArrayBuffer _arrayBuffer to the given attribute location.
     * If that location was already used it will get overwritten.
     * The _attributeLocation has to be lower than GL_MAX_VERTEX_ATTRIBS
140
     * An attribute location of -1 indicates that the attribute should remain unbound for now
141
     */
142
    inline void attachAttribute( const SharedArrayBuffer& _arrayBuffer,
143
                          uint32_t _arrayBufferAttribute,
144
                          GLint   _attributeLocation = -1)
145
146
147
148
149
    {
        Attribute newAttribute = { _arrayBuffer, _arrayBufferAttribute, _attributeLocation };
        attachAttribute( newAttribute );
    }

150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
    /**
     * Will set the attribute named _arrayBufferAttributeName of ArrayBuffer _arrayBuffer to the given attribute location.
     * If that location was already used it will get overwritten.
     * The _attributeLocation has to be lower than GL_MAX_VERTEX_ATTRIBS
     * An attribute location of -1 indicates that the attribute should remain unbound for now
     */
    inline void attachAttribute( const SharedArrayBuffer& _arrayBuffer,
                          const std::string& _arrayBufferAttributeName,
                          GLint   _attributeLocation = -1)
    {
        attachAttribute(_arrayBuffer,
                        _arrayBuffer->getAttributeIndexByName(_arrayBufferAttributeName),
                        _attributeLocation);
    }

165
166
167
168
    void attachAttribute( const Attribute &_attribute )
    {
        GLint maxAttributes;
        glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxAttributes); // TODO: clever caching
169
170
171

        if(mAttributes.size() >= (uint32_t) maxAttributes)
        {
172
173
174
175
176
177
178
            ACGL::Utils::error() << "can't attach attribute " << _attribute.arrayBuffer->getAttributes()[_attribute.attributeID].name
                                 << " - maximum number of attributes reached: " << maxAttributes << std::endl;
            return;
        }

        mAttributes.push_back( _attribute );

179
180
181
182
        enable();
        setAttributePointer( _attribute );
        disable();
    }
183

184
185
186
187
188
189
190
191
192
193
194
195
    /**
     * Attaches all attributes defined by an ArrayBuffer
     * The attributes are attached to the default location of -1, indicating they should remain unbound for now
     * Afterwards, you might want to automatically wire up the attributes with a ShaderProgram, using setAttributeLocationsByShaderProgram
     */
    void attachAllAttributes( const SharedArrayBuffer& _arrayBuffer )
    {
        ArrayBuffer::AttributeVec attributes = _arrayBuffer->getAttributes();
        for(ArrayBuffer::AttributeVec::size_type i = 0; i < attributes.size(); ++i)
        {
            attachAttribute(_arrayBuffer, i);
        }
196
197
198
199
200
201
202
203
204
    }

    /**
     * Will detach the first found Attribute with the given attribute location.
     */
    void detachAttribute( GLint _location )
    {
        for (AttributeVec::size_type i = 0; i < mAttributes.size(); ++i)
        {
205
206
            if (mAttributes[i].location == _location)
            {
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
                // the other pointer data is still set, but that isn't relevant if the attribute itself is deactivated
                glDisableVertexArrayAttribEXT( mObjectName, mAttributes[i].location );
                mAttributes.erase( mAttributes.begin()+i );
                return;
            }
        }
        // if we got here, no Attribute of the given name exists
        ACGL::Utils::warning() << "can't detach attribute with location " << _location << " - no such Attribute" << std::endl;
    }

    /**
     * Will detach the first found Attribute with the given name.
     */
    void detachAttribute( const std::string &_name )
    {
        for (AttributeVec::size_type i = 0; i < mAttributes.size(); ++i)
        {
224
225
            if (mAttributes[i].arrayBuffer->getAttributes()[ mAttributes[i].attributeID ].name == _name)
            {
226
227
228
229
230
231
232
233
234
235
236
237
238
239
                // the other pointer data is still set, but that isn't relevant if the attribute itself is deactivated
                glDisableVertexArrayAttribEXT( mObjectName, mAttributes[i].location );
                mAttributes.erase( mAttributes.begin()+i );
                return;
            }
        }
        // if we got here, no Attribute of the given name exists
        ACGL::Utils::warning() << "can't detach attribute " << _name << " - no such Attribute" << std::endl;
    }

    /**
     * Return true if setAttributeMappingsByShaderProgram(_shaderProgram) would do nothing.
     * That means the VAO and ShaderProgram have already matching layouts.
     */
240
    bool mappingsMatchShaderProgram( const SharedShaderProgram &_shaderProgram ) const
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
    {
        for (AttributeVec::size_type i = 0; i < mAttributes.size(); ++i)
        {
            std::string attributeName  = mAttributes[i].arrayBuffer->getAttributes()[ mAttributes[i].attributeID ].name;
            GLint       shaderLocation = _shaderProgram->getAttributeLocation( attributeName );

            if (mAttributes[i].location != shaderLocation) return false;
        }
        return true;
    }

    /**
     * Query the attribute locations based on the attribute names in the ArrayBuffers and the ShaderProgram
     * If they match, use the location reported from the ShaderProgram.
     */
Robert Menzel's avatar
Robert Menzel committed
256
    /*
257
    void setAttributeLocationsByShaderProgram( const SharedShaderProgram &_shaderProgram )
258
    {
259
        // TODO: deprecate
260
261
262
263
264
265
266
        bool fullUpdateNeeded = false;

        for (AttributeVec::size_type i = 0; i < mAttributes.size(); ++i)
        {
            std::string attributeName  = mAttributes[i].arrayBuffer->getAttributes()[ mAttributes[i].attributeID ].name;
            GLint       shaderLocation = _shaderProgram->getAttributeLocation( attributeName );

267
268
            if (shaderLocation == -1)
            {
269
270
271
272
                ACGL::Utils::error() << "can't update VAO mappings, attribute " << attributeName << " does not exist in shader" << std::endl;
                continue; // try to match as much as possible
            }

273
274
            if (mAttributes[i].location != shaderLocation)
            {
275
276
277
278
279
280
281
                mAttributes[i].location  = shaderLocation;
                fullUpdateNeeded = true;
            }
        }

        // why the full update? setting the new location right when a change is detected will get problamatic
        // if two attributes exchange there position...
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
        if (fullUpdateNeeded)
        {
            enable();

            // disable all attributes
            GLint maxAttributes;
            glGetIntegerv( GL_MAX_VERTEX_ATTRIBS, &maxAttributes );
            for (GLint i = 0; i < maxAttributes; ++i)
                glDisableVertexAttribArray( i );

            // set all attributes:
            for (uint32_t i = 0; i < mAttributes.size(); ++i)
            {
                setAttributePointer(mAttributes[i]);
            }

            disable();
        }
Robert Menzel's avatar
Robert Menzel committed
300
    }*/
301
302

    /**
Robert Menzel's avatar
Robert Menzel committed
303
304
     * Query the attribute locations based on the attribute names in the ArrayBuffers.
     * If they match, use the location reported from _locationMappings.
305
     */
Robert Menzel's avatar
Robert Menzel committed
306
    void setAttributeLocations( ConstSharedLocationMappings _locationMappings );
307

308
309
310
311
private:
    //! Sets the vertex attribute pointer for the current VAO according to the specified attribute data
    //! Note: expects that this VAO is currently bound
    //! Note: will bind the ArrayBuffer referenced by _attribute.arrayBuffer
Robert Menzel's avatar
Robert Menzel committed
312
    void setAttributePointer(const Attribute& _attribute);
313

314
315
316
317
318
319
320
321
322
323
324
    // ===================================================================================================== \/
    // ============================================================================================ WRAPPERS \/
    // ===================================================================================================== \/
public:
    //! Bind this VAO
    inline void bind(void) const
    {
        glBindVertexArray( mObjectName );
    }

    //! Nothing has to be prepared for a render call
Robert Menzel's avatar
Robert Menzel committed
325
    inline void render (void)
326
327
328
329
330
331
    {
        enable();
        draw();
        disable();
    }

Robert Menzel's avatar
Robert Menzel committed
332
    //! Will select the matching draw call. Remember to bind() first!
333
334
335
336
337
338
339
340
341
342
343
    void draw(void) const
    {
        if(mpElementArrayBuffer)
            drawElements();
        else
            drawArrays();

        openGLRareError();
    }

    //! Can be called directly instead of draw() iff the caller knows this is the correct call!
344
345
346
347
348
349
350
    inline void drawElements(GLsizei _elements, const GLvoid* _offset = 0) const
    {
        glDrawElements(mMode, _elements, mpElementArrayBuffer->getType(), _offset);
    }

    //! Can be called directly instead of draw() iff the caller knows this is the correct call!
    //! Draws all elements
351
352
    inline void drawElements(void) const
    {
353
        drawElements(mpElementArrayBuffer->getElements());
354
355
356
    }

    //! Can be called directly instead of draw() iff the caller knows this is the correct call!
357
358
359
360
361
362
363
    inline void drawArrays(GLsizei _elements, GLint _offset = 0) const
    {
        glDrawArrays(mMode, _offset, _elements);
    }

    //! Can be called directly instead of draw() iff the caller knows this is the correct call!
    //! Draws all elements
364
365
    inline void drawArrays(void) const
    {
Robert Menzel's avatar
Robert Menzel committed
366
367
368
369
370
        if(mAttributes.empty())
        {
            ACGL::Utils::error() << "cannot draw VAO with no attributes attached" << std::endl;
            return;
        }
371
        drawArrays(mAttributes[0].arrayBuffer->getElements());
372
373
374
375
376
    }

    // =================================================================================================== \/
    // ============================================================================================ FIELDS \/
    // =================================================================================================== \/
Robert Menzel's avatar
Robert Menzel committed
377
private:
378
379
380
    GLuint                   mObjectName;          // OpenGL object name
    SharedElementArrayBuffer mpElementArrayBuffer; // optional EAB
    AttributeVec             mAttributes;          // vertex attributes
381
382
    GLenum                   mMode;                // primitive type to render (e.g. GL_TRIANGLES)

Robert Menzel's avatar
Robert Menzel committed
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
    //! Bind this VAO and remember the previously bound VAO
    //! Note: every call to this method must be paired with a corresponding call to disable()
    inline void enable(void)
    {
        // remember old VAO
        glGetIntegerv( GL_VERTEX_ARRAY_BINDING, &mPreviousVAOName );
        if (mObjectName != (GLuint)mPreviousVAOName) bind();
    }

    //! Bind the VAO that was bound before the most recent enable() call
    inline void disable(void)
    {
        if (mObjectName != (GLuint)mPreviousVAOName) glBindVertexArray((GLuint)mPreviousVAOName);
    }

    GLint mPreviousVAOName;     // the VAO that was bound before the last enable() call
399
400
401
402
403
404
405
406
407
408
};

ACGL_SHARED_TYPEDEF(VertexArrayObject)

} // OpenGL
} // ACGL

#endif // OpenGL 3.0

#endif // ACGL_OPENGL_OBJECTS_VERTEXARRAYOBJECT_HH