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,
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                 enum fullName = "Vertex." ~ member;
65                 mixin("alias T = typeof(" ~ fullName ~ ");");
66 
67                 static if (staticIndexOf!(T, TT) != -1)
68                 {
69                     int location = program.attrib(member).location;
70                     mixin("enum size_t offset = Vertex." ~ member ~ ".offsetof;");
71 
72                     enum UDAs = __traits(getAttributes, member);
73                     bool normalize = (staticIndexOf!(Normalized, UDAs) == -1);
74 
75                     // detect suitable type
76                     int n;
77                     GLenum glType;
78                     toGLTypeAndSize!T(glType, n);
79                     _attributes ~= VertexAttribute(n, offset, glType, location, normalize ? GL_TRUE : GL_FALSE);
80 
81                 }
82             }
83         }
84 
85         /**
86          * Use this vertex specification.
87          *
88          * Divisor is the value passed to glVertexAttribDivisor.
89          * See $(WEB www.opengl.org/wiki/Vertex_Specification#Instanced_arrays) for details.
90          *
91          * Throws: $(D OpenGLException) on error.
92          */
93         void use(GLuint divisor = 0)
94         {
95             // use every attribute
96             for (uint i = 0; i < _attributes.length; ++i)
97                 _attributes[i].use(_gl, cast(GLsizei) vertexSize(), divisor);
98         }
99 
100         /// Unuse this vertex specification. If you are using a VAO, you don't need to call it,
101         /// since the attributes would be tied to the VAO activation.
102         /// Throws: $(D OpenGLException) on error.
103         void unuse()
104         {
105             // unuse all the attributes
106             for (uint i = 0; i < _attributes.length; ++i)
107                 _attributes[i].unuse(_gl);
108         }
109 
110         /// Returns the size of the Vertex; this size can be computer
111         /// after you added all your attributes
112         size_t vertexSize() pure const nothrow
113         {
114             return Vertex.sizeof;
115         }
116     }
117 
118     private
119     {
120         OpenGL _gl;
121         GLProgram _program;
122         VertexAttribute[] _attributes;
123     }
124 }
125 
126 /// Describes a single attribute in a vertex entry.
127 struct VertexAttribute
128 {
129     private
130     {
131         int n;
132         size_t offset;
133         GLenum glType;
134         GLint location;
135         GLboolean normalize;
136         bool divisorSet;
137 
138 
139         /// Use this attribute.
140         /// Throws: $(D OpenGLException) on error.
141         void use(OpenGL gl, GLsizei sizeOfVertex, GLuint divisor)
142         {
143             // fake attribute, do not enable
144             if (location == GLAttribute.fakeLocation)
145                 return ;
146 
147             if (divisor != 0)
148                 divisorSet = true;
149 
150             glEnableVertexAttribArray(location);
151             if (isIntegerType(glType))
152                 glVertexAttribIPointer(location, n, glType, sizeOfVertex, cast(GLvoid*)offset);
153             else
154                 glVertexAttribPointer(location, n, glType, normalize, sizeOfVertex, cast(GLvoid*)offset);
155             if(divisorSet)
156                 glVertexAttribDivisor(location, divisor);
157             gl.runtimeCheck();
158         }
159 
160         /// Unuse this attribute.
161         /// Throws: $(D OpenGLException) on error.
162         void unuse(OpenGL gl)
163         {
164             // couldn't figure out if glDisableVertexAttribArray resets this, so play it safe
165             if(divisorSet)
166                 glVertexAttribDivisor(location, 0);
167             divisorSet = false;
168             glDisableVertexAttribArray(location);
169             gl.runtimeCheck();
170         }
171     }
172 }
173 
174 private
175 {
176     bool isIntegerType(GLenum t)
177     {
178         return (t == GL_BYTE
179              || t == GL_UNSIGNED_BYTE
180              || t == GL_SHORT
181              || t == GL_UNSIGNED_SHORT
182              || t == GL_INT
183              || t == GL_UNSIGNED_INT);
184     }
185 
186     alias VectorTypes = TypeTuple!(byte, ubyte, short, ushort, int, uint, float, double);
187     enum GLenum[] VectorTypesGL =
188     [
189         GL_BYTE,
190         GL_UNSIGNED_BYTE,
191         GL_SHORT,
192         GL_UNSIGNED_SHORT,
193         GL_INT,
194         GL_UNSIGNED_INT,
195         GL_FLOAT,
196         GL_DOUBLE
197     ];
198 
199     template typeToGLScalar(T)
200     {
201         alias U = Unqual!T;
202         enum index = staticIndexOf!(U, VectorTypes);
203         static if (index == -1)
204         {
205             static assert(false, "Could not use " ~ T.stringof ~ " in a vertex description");
206         }
207         else
208             enum typeToGLScalar = VectorTypesGL[index];
209     }
210 
211     void toGLTypeAndSize(T)(out GLenum type, out int n)
212     {
213         static if (isStaticArray!T)
214         {
215             type = typeToGLScalar(typeof(T[0]));
216             n = U.length;
217         }
218         else
219         {
220             alias U = Unqual!T;
221 
222             // is it a gfm.vector.Vector?
223             foreach(int t, S ; VectorTypes)
224             {
225                 static if (is (U == Vector!(S, 2)))
226                 {
227                     type = VectorTypesGL[t];
228                     n = 2;
229                     return;
230                 }
231 
232                 static if (is (U == Vector!(S, 3)))
233                 {
234                     type = VectorTypesGL[t];
235                     n = 3;
236                     return;
237                 }
238 
239                 static if (is (U == Vector!(S, 4)))
240                 {
241                     type = VectorTypesGL[t];
242                     n = 4;
243                     return;
244                 }
245             }
246 
247             assert(false, "Could not use " ~ T.stringof ~ " in a vertex description");
248         }
249     }
250 }