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.opengl;
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 isSupportedScalarType(T)
204     {
205         enum isSupportedScalarType = staticIndexOf!(Unqual!T, VectorTypes) != -1;
206     }
207 
208     template typeToGLScalar(T)
209     {
210         alias U = Unqual!T;
211         enum index = staticIndexOf!(U, VectorTypes);
212         static if (index == -1)
213         {
214             static assert(false, "Could not use " ~ T.stringof ~ " in a vertex description");
215         }
216         else
217             enum typeToGLScalar = VectorTypesGL[index];
218     }
219 
220     void toGLTypeAndSize(T)(out GLenum type, out int n)
221     {
222         static if (isSupportedScalarType!T)
223         {
224             type = typeToGLScalar!T;
225             n = 1;
226         }
227         else static if (isStaticArray!T)
228         {
229             type = typeToGLScalar!(typeof(T.init[0]));
230             n = T.length;
231         }
232         else
233         {
234             alias U = Unqual!T;
235 
236             // is it a gfm.vector.Vector?
237             foreach(int t, S ; VectorTypes)
238             {
239                 static if (is (U == Vector!(S, 2)))
240                 {
241                     type = VectorTypesGL[t];
242                     n = 2;
243                     return;
244                 }
245 
246                 static if (is (U == Vector!(S, 3)))
247                 {
248                     type = VectorTypesGL[t];
249                     n = 3;
250                     return;
251                 }
252 
253                 static if (is (U == Vector!(S, 4)))
254                 {
255                     type = VectorTypesGL[t];
256                     n = 4;
257                     return;
258                 }
259             }
260 
261             assert(false, "Could not use " ~ T.stringof ~ " in a vertex description");
262         }
263     }
264 }
265 
266 
267 /// A helper template to define a simple vertex type from a vector type.
268 /// By default the unique field will be name "position".
269 /// Note: it's important the struct isn't larger than the vector itself.
270 ///
271 /// Example:
272 ///     VertexSpecification!(VertexPosition!vec3f);
273 ///
274 align(1) struct VertexPosition(Vec, string fieldName = "position") if (isVector!Vec)
275 {
276     align(1):
277     mixin("Vec " ~ fieldName ~ ";");
278     static assert(VertexPosition.sizeof == Vec.sizeof);
279 }
280 
281 unittest
282 {
283    static struct MyVertex
284    {
285        vec3f position;
286        vec4f diffuse;
287        float shininess;
288        @Normalized vec2i uv;
289        vec3f normal;
290    }
291    alias Test = VertexSpecification!MyVertex;
292 
293    alias Test2 = VertexSpecification!(VertexPosition!vec3f);
294 }