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 }