1 module gfmod.opengl.vertexarray;
2 
3 import std..string;
4 import std.traits;
5 import std.typecons;
6 import std.typetuple;
7 
8 import derelict.opengl3.gl3;
9 
10 import gfmod.opengl.opengl;
11 import gfmod.opengl.program;
12 
13 import gl3n.linalg;
14 
15 
16 /// Primitive types that may be stored in a VertexArray.
17 enum PrimitiveType: GLenum
18 {
19     Points        = GL_POINTS,
20     LineStrip     = GL_LINE_STRIP,
21     LineLoop      = GL_LINE_LOOP,
22     Lines         = GL_LINES,
23     TriangleStrip = GL_TRIANGLE_STRIP,
24     TriangleFan   = GL_TRIANGLE_FAN,
25     Triangles     = GL_TRIANGLES
26 }
27 
28 
29 /// Possible types of a single element of a vertex attribute.
30 alias AttributeElementTypes = TypeTuple!(float, double, byte, short, int, ubyte, ushort, uint);
31 
32 /// GL types corresponding to items in AttributeElementTypes.
33 enum attributeElementGLTypes = [GL_FLOAT, GL_DOUBLE, 
34                                 GL_BYTE, GL_SHORT, GL_INT,
35                                 GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_UNSIGNED_INT];
36 
37 /// Determine if a type can be an attribute (aka field, aka property) of a vertex type.
38 template isVertexAttribute(A)
39 {
40     // gl3n.linalg.Vector has vt (element type) and dimension.
41     // We're not likely to accidentally hit another type with the same combination...
42     // and if we do, it's probably someone intentionally making a compatible type.
43     static if(hasMember!(A, "vt") && hasMember!(A, "dimension"))
44     {
45         enum isVertexAttribute = staticIndexOf!(A.vt, AttributeElementTypes) != -1 &&
46                                  A.dimension >= 1 && A.dimension <= 4;
47     }
48     else
49     {
50         // Scalar types should work too.
51         enum isVertexAttribute = staticIndexOf!(A, AttributeElementTypes) != -1;;
52     }
53 }
54 
55 /// Determine GL type of a vertex attribute.
56 template glType(A)
57     if(isVertexAttribute!A)
58 {
59     alias ElemType = Select!(hasMember!(A, "vt"), A.vt, A);
60     enum glType = attributeElementGLTypes[staticIndexOf!(ElemType, AttributeElementTypes)];
61 }
62 
63 /// Determine the dimensionality if a vertex attribute.
64 template dimension(A)
65     if(isVertexAttribute!A)
66 {
67     enum dimension = Select!(hasMember!(A, "dimension"), A.dimension, A);
68 }
69 
70 //XXX the VertexArray type can be extended further:
71 //    Support multiple template args (not just 'V'). If there are multiple template args,
72 //    their attributes will be in separate VBOs. It will be then possible to add just
73 //    to those VBOs with add(), and to access just those VBOs memory buffers by data().
74 //
75 //    This way we can have both interleaved and separate VBOs, and anything in between.
76 
77 /// Determine if a type is a valid vertex type.
78 ///
79 /// A vertex type must be a plain-old-data struct with no custom
80 /// destructor/copyctor/assign and all its data members must be 1 to 4-dimensional
81 /// gl3n.linalg.Vectors such as vec3 or Vector!(4, ubyte).
82 enum isVertex(V) = is(V == struct) &&
83                    !hasElaborateDestructor!V &&
84                    !hasElaborateCopyConstructor!V &&
85                    !hasElaborateAssign!V &&
86                    allSatisfy!(isVertexAttribute, FieldTypeTuple!V);
87 
88 // TODO: Currently, integral vertex attributes are automatically normalized into 
89 //       the 0-1 range. Add a @nonormalize UDA to be able to disable this 
90 //       (e.g. @nonormalize vec4ub thisIsNotAColor) 2014-08-14
91 
92 /// A wrapper around GL Vertex Attribute Object that also manages its vertex storage.
93 ///
94 /// Acts as a dynamic array of vertex type V. 
95 ///
96 /// V must be a plain-old-data struct where each member is either a gl3n Vector
97 /// (such as vec3) or a scalar number (such as float).
98 /// 
99 /// Example vertex type:
100 ///
101 /// --------------------
102 /// struct Vertex
103 /// {
104 ///     vec3 position;
105 ///     vec3 normal;
106 ///     Vector!(ubyte, 4) rgbaColor;
107 /// }
108 /// --------------------
109 ///
110 /// Vertex attributes must be either one of the following types: $(I float, double,
111 /// byte, short, int, ubyte, ushort, uint) or a 4 or less dimensional gl3n.linalg.Vector
112 /// with type parameter set to one of listed types. Note that by default, attributes
113 /// with integral values will have those values normalized into the [0-1] range (for
114 /// example, a color channel with value of 255 will be normalized into 1.0). In future,
115 /// an UDA (TODO) will be added to allow the user to disable normalization of integers.
116 ///
117 /// The VertexArray requires a shader program when being bound and looks for vertex attributes
118 /// with names corresponding to fields of V. Any shader program used to draw a VertexArray must
119 /// contain vertex attributes for all members of V (otherwise VertexArray binding will fail).
120 ///
121 /// For example, the following vertex shader source has all members specified by the
122 /// $(D Vertex) struct in the above example:
123 ///
124 /// --------------------
125 /// #version 130
126 ///
127 /// in vec3 position;
128 /// in vec3 normal;
129 /// in vec4 rgbaColor;
130 ///
131 /// void main()
132 /// {
133 ///     // ... do stuff here ...
134 /// }
135 /// --------------------
136 final class VertexArray(V)
137     if(isVertex!V)
138 {
139 private:
140     // The GL VAO handle.
141     GLuint vao_;
142     // Handle to the GL VBO used to store all vertex attributes.
143     GLuint vbo_;
144 
145     // OpenGL info and logging.
146     OpenGL gl_;
147 
148     // Storage for a RAM copy of VBO data.
149     V[] storage_;
150     // Used part of storage_.
151     V[] vertices_;
152 
153     // Current state of the VertexArray.
154     State state_ = State.Unlocked;
155 
156     // The program that was last used to draw data from the VertexArray.
157     //
158     // Needed to check if the user is changing programs (in which case we need to
159     // reload vertex attributes from the program).
160     GLProgram lastProgram_ = null;
161 
162     // True if _any_ VertexArray is bound. Used to avoid collisions between VAOs.
163     static bool isAnyVAOBound_ = false;
164 
165     // We can, so why not?
166     import std.range: isOutputRange;
167     static assert(isOutputRange!(typeof(this), V), "VertexArray must be an OutputRange");
168 
169 public:
170     /// Possible states a VertexArray can be in.
171     enum State
172     {
173         /// The VertexArray can be modified but not bound or drawn.
174         Unlocked,
175         /// The VertexArray can not be modified, but can be bound.
176         Locked,
177         /// The VertexArray can't be modified and can be drawn.
178         Bound
179     }
180 
181     /** Construct a VertexArray.
182      *
183      * Params:
184      *
185      * gl      = The OpenGL wrapper.
186      * storage = Space to store vertices in (we need to store a copy of all vertex data
187      *           in RAM to allow easy modification). Determines the maximum number of
188      *           vertices the VertexArray can hold. The VertexArray $(B will not) 
189      *           deallocate this space when destroyed; the caller must take care of that.
190      */
191     this(OpenGL gl, V[] storage) @trusted nothrow @nogc
192     {
193         gl_       = gl;
194         storage_  = storage;
195         vertices_ = storage_[0 .. 0];
196 
197         glGenBuffers(1, &vbo_);
198         glGenVertexArrays(1, &vao_);
199     }
200 
201     /** Destroy the VertexArray.
202      *
203      * Must be destroyed by the user to ensure all used GL objects are deleted.
204      */
205     ~this() @trusted nothrow @nogc
206     {
207         glDeleteVertexArrays(1, &vao_);
208         glDeleteBuffers(1, &vbo_);
209     }
210 
211     /** Add a vertex to the VertexArray.
212      *
213      * Must not add any more vertices if VertexArray.length == VertexArray.capacity.
214      * Must not be called when the VertexArray is locked.
215      */
216     void put(const V vertex) @safe pure nothrow @nogc
217     {
218         assert(state_ == State.Unlocked, "Trying to add a vertex to a locked VertexArray");
219         const length = vertices_.length;
220         if(length >= storage_.length) { assert(false, "This VertexArray is already full"); }
221 
222         storage_[length] = vertex;
223         vertices_ = storage_[0 .. length + 1];
224     }
225 
226     /** Get direct access to vertices in the VertexArray.
227      *
228      * Can be used for fast modification of the VertexArray. Any modifications to the slice
229      * after the VertexArray is locked will result in $(B undefined behavior).
230      */
231     V[] data() @system pure nothrow @nogc
232     {
233         assert(state_ == State.Unlocked,
234                "Trying to get direct access to contents of a locked VertexArray");
235         return vertices_;
236     }
237 
238     /// Get the current number of vertices in the VertexArray.
239     size_t length() @safe pure nothrow const @nogc
240     {
241         return vertices_.length;
242     }
243 
244     /** Manually set the length of the VertexArray.
245      *
246      * Params:
247      *
248      * rhs = The new length of the VertexArray. Must be <= capacity. If used to increase the
249      *       length, the new elements of the VertexArray ([oldLength .. newLength]) will have
250      *       $(B uninitialized values).
251      */
252     void length(size_t rhs) @system pure nothrow @nogc
253     {
254         assert(state_ == State.Unlocked, "Trying to set length of a locked VertexArray");
255         assert(rhs <= storage_.length, "Can't extend VertexArray length further than capacity");
256         vertices_ = storage_[0 .. rhs];
257     }
258 
259     /** Get the maximum number of vertices the VertexArray can hold.
260      *
261      * If VertexArray.length equals this value, no more vertices can be added.
262      */
263     size_t capacity() @safe pure nothrow const @nogc { return storage_.length; }
264 
265     /// Is the VertexArray empty (no vertices) ?
266     bool empty() @safe pure nothrow const @nogc { return length == 0; }
267 
268     /** Clear the VertexArray, deleting all vertices.
269      *
270      * Can only be called while the VertexArray is unlocked.
271      */
272     void clear() @trusted pure nothrow @nogc { length = 0; }
273 
274     /** Draw vertices from the VertexArray directly, without using indices.
275      *
276      * This is the only way to draw if the VertexArray has no index type.
277      *
278      * Can only be called when the VertexArray is bound.
279      *
280      * Params:
281      *
282      * type  = Type of primitives to draw.
283      * first = Index of the first vertex to draw.
284      * count = Number of vertices to draw.
285      *
286      * first + count <= VertexArray.length() must be true.
287      */
288     void draw(PrimitiveType type, size_t first, size_t count)
289         @trusted nothrow @nogc
290     {
291         assert(state_ == State.Bound, "Trying to draw a VertexArray that is not bound");
292         assert(first + count <= length, "VertexArray draw call out of range.");
293         glDrawArrays(cast(GLenum)type, cast(int)first, cast(int)count);
294     }
295 
296     /** Lock the buffer.
297      *
298      * Must be called before binding the buffer for drawing.
299      *
300      * It is a good practice to keep a buffer locked for a long time.
301      */
302     void lock() @trusted nothrow @nogc
303     {
304         assert(state_ == State.Unlocked, "Trying to lock a VertexArray that's already locked");
305 
306         // Ensure that if anything is bound, it stays bound when we're done.
307         GLint oldBound;
308         glGetIntegerv(GL_ARRAY_BUFFER_BINDING,  &oldBound);
309         scope(exit) { glBindBuffer(GL_ARRAY_BUFFER, oldBound); }
310 
311         glBindBuffer(GL_ARRAY_BUFFER, vbo_);
312         glBufferData(GL_ARRAY_BUFFER, vertices_.length * V.sizeof, vertices_.ptr,
313                      GL_STATIC_DRAW);
314 
315         state_ = State.Locked;
316     }
317 
318     /** Unlock the buffer.
319      *
320      * Must be called before modifying the buffer if it was locked previously.
321      */
322     void unlock() @safe pure nothrow @nogc 
323     {
324         assert(state_ == State.Locked,
325                "Trying to unlock a buffer that is either bound or not locked");
326         state_ = State.Unlocked;
327     }
328 
329     /** Bind the VertexArray for drawing. Must be called before drawing. VertexArray
330      *  must be locked.
331      *
332      * Only one VertexArray can be bound at a time. It must be released before binding another
333      * VertexArray.
334      *
335      * Params:
336      *
337      * program = The vertex program that will be used to draw data from this VertexArray.
338      *           Needed for the VertexArray to specify which data corresponds to which
339      *           attributes.
340      *
341      * Returns: true on success, false on failure (not all vertex attributes found in
342      *          the program).
343      */
344     bool bind(GLProgram program) @trusted nothrow @nogc
345     {
346         assert(state_ == State.Locked,
347                "Trying to bind a VertexArray that is either already bound or not locked");
348 
349         // TODO: Once moved to newer than GL 3.0, remove isAnyVAOBound and use
350         // glGetIntegerv(GL_VERTEX_ARRAY_BINDING) to ensure nothing else is bound
351         // at the moment 2014-08-12
352         assert(!isAnyVAOBound_, "Another VertexArray is bound already");
353 
354         if(program !is lastProgram_)
355         {
356             // Temp, until we have a tharsis.util package.
357             import gfmod.opengl.uniform: FieldNamesTuple;
358             alias fieldNames = FieldNamesTuple!V;
359 
360             // Need to check if we have all attribs before we start messing with the
361             // VAO.
362             foreach(name; fieldNames) if(!program.hasAttrib(name))
363             {
364                 return false;
365             }
366 
367             glBindVertexArray(vao_);
368             glBindBuffer(GL_ARRAY_BUFFER, vbo_);
369 
370             // Tell the VAO where in the VBO data for each vertex attrib is.
371             enum int stride = V.sizeof;
372             size_t offset = 0;
373             foreach(index, Attrib; FieldTypeTuple!V)
374             {
375                 enum name    = fieldNames[index];
376                 const attrib = program.attrib(name);
377                 glEnableVertexAttribArray(attrib.location);
378                 glVertexAttribPointer(attrib.location,
379                                       dimension!Attrib,
380                                       glType!Attrib,
381                                       isIntegral!(Attrib.vt) ? GL_TRUE : GL_FALSE,
382                                       stride, 
383                                       cast(void*)offset);
384                 offset += Attrib.sizeof;
385             }
386             glBindVertexArray(0);
387             // Rebind, just to be sure.
388             glBindBuffer(GL_ARRAY_BUFFER, 0);
389             lastProgram_ = program;
390         }
391 
392         glBindVertexArray(vao_);
393 
394         isAnyVAOBound_ = true;
395         state_         = State.Bound;
396         return true;
397     }
398 
399     /** Release the buffer after drawing.
400      *
401      * Must be called before making any modifications to the buffer.
402      */
403     void release() @trusted nothrow @nogc
404     {
405         assert(state_ == State.Bound, "Trying to release a VertexArray that is not bound");
406 
407         glBindVertexArray(0);
408         state_         = State.Locked;
409         isAnyVAOBound_ = false;
410     }
411 }