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