1 module gfm.opengl.vertex;
2 
3 
4 // Describing vertex, submitting geometry.
5 // Implement so called Vertex Arrays and VBO (which are Vertex Arrays + OpenGL buffers)
6 
7 import std.string,
8        std.typetuple,
9        std.typecons,
10        std.traits;
11 
12 import derelict.opengl3.gl3;
13 
14 import gfm.math.vector,
15        gfm.opengl.opengl,
16        gfm.opengl.program,
17        gfm.opengl.buffer;
18 
19 
20 /// An UDA to specify an attribute which has to be normalized
21 struct Normalized
22 {
23 }
24 
25 /**
26  * A VertexSpecification's role is to describe a Vertex structure.
27  * You must instantiate it with a compile-time struct describing your vertex format.
28  *
29  * Examples:
30  * --------------------
31  * struct MyVertex
32  * {
33  *     vec3f position;
34  *     vec4f diffuse;
35  *     float shininess;
36  *     @Normalized vec2i uv;
37  *     vec3f normal;
38  * }
39  * --------------------
40  * Member names MUST match those used in the vertex shader as attributes.
41  */
42 final class VertexSpecification(Vertex)
43 {
44     public
45     {
46         /// Creates a vertex specification.
47         /// The program is used to find the attribute location.
48         this(GLProgram program)
49         {
50             _gl = program._gl;
51             _program  = program;
52 
53             template isRWField(T, string M)
54             {
55                 enum isRWField = __traits(compiles, __traits(getMember, Tgen!T(), M) = __traits(getMember, Tgen!T(), M));
56                 pragma(msg, T.stringof~"."~M~": "~(isRWField?"1":"0"));
57             }
58 
59             alias TT = FieldTypeTuple!Vertex;
60 
61             // Create all attribute description
62             foreach (member; __traits(allMembers, Vertex))
63             {
64                 // Nested struct have a context pointer
65                 static assert(member != "this",
66                               `Found a 'this' member in vertex struct. Use a 'static struct' instead.`);
67 
68                 enum fullName = "Vertex." ~ member;
69                 mixin("alias T = typeof(" ~ fullName ~ ");");
70 
71                 static if (staticIndexOf!(T, TT) != -1)
72                 {
73                     int location = program.attrib(member).location;
74                     mixin("enum size_t offset = Vertex." ~ member ~ ".offsetof;");
75 
76                     enum UDAs = __traits(getAttributes, member);
77                     bool normalize = (staticIndexOf!(Normalized, UDAs) == -1);
78 
79                     // detect suitable type
80                     int n;
81                     GLenum glType;
82                     toGLTypeAndSize!T(glType, n);
83                     _attributes ~= VertexAttribute(n, offset, glType, location, normalize ? GL_TRUE : GL_FALSE);
84 
85                 }
86             }
87         }
88 
89         /**
90          * Use this vertex specification.
91          *
92          * Divisor is the value passed to glVertexAttribDivisor.
93          * See $(WEB www.opengl.org/wiki/Vertex_Specification#Instanced_arrays) for details.
94          *
95          * Throws: $(D OpenGLException) on error.
96          */
97         void use(GLuint divisor = 0)
98         {
99             // use every attribute
100             for (uint i = 0; i < _attributes.length; ++i)
101                 _attributes[i].use(_gl, cast(GLsizei) vertexSize(), divisor);
102         }
103 
104         /// Unuse this vertex specification. If you are using a VAO, you don't need to call it,
105         /// since the attributes would be tied to the VAO activation.
106         /// Throws: $(D OpenGLException) on error.
107         void unuse()
108         {
109             // unuse all the attributes
110             for (uint i = 0; i < _attributes.length; ++i)
111                 _attributes[i].unuse(_gl);
112         }
113 
114         /// Returns the size of the Vertex; this size can be computer
115         /// after you added all your attributes
116         size_t vertexSize() pure const nothrow
117         {
118             return Vertex.sizeof;
119         }
120     }
121 
122     private
123     {
124         OpenGL _gl;
125         GLProgram _program;
126         VertexAttribute[] _attributes;
127     }
128 }
129 
130 /// Describes a single attribute in a vertex entry.
131 struct VertexAttribute
132 {
133     private
134     {
135         int n;
136         size_t offset;
137         GLenum glType;
138         GLint location;
139         GLboolean normalize;
140         bool divisorSet;
141 
142 
143         /// Use this attribute.
144         /// Throws: $(D OpenGLException) on error.
145         void use(OpenGL gl, GLsizei sizeOfVertex, GLuint divisor)
146         {
147             // fake attribute, do not enable
148             if (location == GLAttribute.fakeLocation)
149                 return ;
150 
151             if (divisor != 0)
152                 divisorSet = true;
153 
154             glEnableVertexAttribArray(location);
155             if (isIntegerType(glType))
156                 glVertexAttribIPointer(location, n, glType, sizeOfVertex, cast(GLvoid*)offset);
157             else
158                 glVertexAttribPointer(location, n, glType, normalize, sizeOfVertex, cast(GLvoid*)offset);
159             if(divisorSet)
160                 glVertexAttribDivisor(location, divisor);
161             gl.runtimeCheck();
162         }
163 
164         /// Unuse this attribute.
165         /// Throws: $(D OpenGLException) on error.
166         void unuse(OpenGL gl)
167         {
168             // couldn't figure out if glDisableVertexAttribArray resets this, so play it safe
169             if(divisorSet)
170                 glVertexAttribDivisor(location, 0);
171             divisorSet = false;
172             glDisableVertexAttribArray(location);
173             gl.runtimeCheck();
174         }
175     }
176 }
177 
178 private
179 {
180     bool isIntegerType(GLenum t)
181     {
182         return (t == GL_BYTE
183              || t == GL_UNSIGNED_BYTE
184              || t == GL_SHORT
185              || t == GL_UNSIGNED_SHORT
186              || t == GL_INT
187              || t == GL_UNSIGNED_INT);
188     }
189 
190     alias VectorTypes = TypeTuple!(byte, ubyte, short, ushort, int, uint, float, double);
191     enum GLenum[] VectorTypesGL =
192     [
193         GL_BYTE,
194         GL_UNSIGNED_BYTE,
195         GL_SHORT,
196         GL_UNSIGNED_SHORT,
197         GL_INT,
198         GL_UNSIGNED_INT,
199         GL_FLOAT,
200         GL_DOUBLE
201     ];
202 
203     template typeToGLScalar(T)
204     {
205         alias U = Unqual!T;
206         enum index = staticIndexOf!(U, VectorTypes);
207         static if (index == -1)
208         {
209             static assert(false, "Could not use " ~ T.stringof ~ " in a vertex description");
210         }
211         else
212             enum typeToGLScalar = VectorTypesGL[index];
213     }
214 
215     void toGLTypeAndSize(T)(out GLenum type, out int n)
216     {
217         static if (isStaticArray!T)
218         {
219             type = typeToGLScalar(typeof(T[0]));
220             n = U.length;
221         }
222         else
223         {
224             alias U = Unqual!T;
225 
226             // is it a gfm.vector.Vector?
227             foreach(int t, S ; VectorTypes)
228             {
229                 static if (is (U == Vector!(S, 2)))
230                 {
231                     type = VectorTypesGL[t];
232                     n = 2;
233                     return;
234                 }
235 
236                 static if (is (U == Vector!(S, 3)))
237                 {
238                     type = VectorTypesGL[t];
239                     n = 3;
240                     return;
241                 }
242 
243                 static if (is (U == Vector!(S, 4)))
244                 {
245                     type = VectorTypesGL[t];
246                     n = 4;
247                     return;
248                 }
249             }
250 
251             assert(false, "Could not use " ~ T.stringof ~ " in a vertex description");
252         }
253     }
254 }
255 
256 
257 /// A helper template to define a simple vertex type from a vector type.
258 /// By default the unique field will be name "position".
259 /// Note: it's important the struct isn't larger than the vector itself.
260 ///
261 /// Example:
262 ///     VertexSpecification!(VertexPosition!vec3f);
263 ///
264 align(1) struct VertexPosition(Vec, string fieldName = "position") if (isVector!Vec)
265 {
266     align(1):
267     mixin("Vec " ~ fieldName ~ ";");
268     static assert(VertexPosition.sizeof == Vec.sizeof);
269 }
270 
271 unittest
272 {
273    static struct MyVertex
274    {
275        vec3f position;
276        vec4f diffuse;
277        float shininess;
278        @Normalized vec2i uv;
279        vec3f normal;
280    }
281    alias Test = VertexSpecification!MyVertex;
282 
283    alias Test2 = VertexSpecification!(VertexPosition!vec3f);
284 }