/*! * /\ +------------------------------------------------------+ * ____/ \____ /| - Open source game framework licensed to freely use, | * \ / / | copy, modify and sell without restriction | * +--\ ^__^ /--+ | | * | ~/ \~ | | - created for | * | ~~~~~~~~~~~~ | +------------------------------------------------------+ * | SPACE ~~~~~ | / * | ~~~~~~~ BOX |/ * +--------------+ * * Attributes * ========== * * An Attributes object is a container that acts as a limited, but dynamically typed vector for * vertex properties to be passed to a GL buffer object and described by a vertex array object. * It uses the variant library and type inference to determine the vertex type for an object. The * appropriate variant will be applied based on the first vertices submitted to the object. * * The object can contain float, int, or unsigned int vertices of any dimension from 1D to 4D. It * can also contain bool vertices from 2D to 4D. Vertices can be submitted at initialization through * the constructor or added and appended later using the add function. If the object is initialized * without any vertices, it will be in an empty state indicated by the std::monostate variant * alternative. * * The constructor accepts initializer lists, so unknown types can be submitted as long as a single type * can be inferred. For example, * * Attributes({1.0f, 0.5f, -1.0f}); // glm::vec3 attributes containing one vertex * Attributes({ // glm::vec3 attributes containing two vertices * {1.0f, 0.5f, -1.0f}, * {0.0f, 1.0f, 1.0f} * }); * Attributes({1, 2}); // glm::ivec2 attributes containing one vertex * Attributes(glm::uvec2{1, 2}); // glm::uvec2 attributes containing one vertex * Attributes(1, 2); // same as Attributes({1, 2}), each argument is treated as a coordinate * Attributes({ // inferred as glm::uvec3, so the -5, -555, and the decimal precision are lost * {-5, 55, -555}, * glm::uvec3{8, 88, 888}, * {9.9, 9.99, 9.999} * }); * * The add function also accepts initializer lists and any type that can be converted into the original. * * sb::Attributes attr, attr2, attr3, attr4; * attr.add(420); * attr.add({69, 9000}); * attr.add({}); * attr.add({{1}}); * std::cout << attr << std::endl; * attr2.add(glm::ivec4{1, 1, 2, 3}); * attr2.add({{5.0f, 8.0f, 13.0f, 21.0f}, {34.0f, 55.0f, 89.0f, 144.0f}}); * attr2.add({glm::uvec4{5, 8, 13, 21}, glm::uvec4{34, 55, 89, 144}}); * attr2.add({0.0f, 0.0f, 0.0f, 0.0f}); * std::cout << attr2 << std::endl; * attr3.add(1.1f); * attr3.add(2); * attr3.add(std::vector{3, 4}); * std::cout << attr3 << std::endl; * attr3 = attr2; * attr2 = attr4; * std::cout << attr3 << std::endl; * std::cout << attr2 << std::endl; * attr4 = sb::Attributes({{-5, 55, -555}, glm::ivec3{8, 88, 888}, {9.99, 9.9, 9.0}}); * attr4.add(1000, 10000, 100000); * attr4.add(1, 11); * std::cout << attr4 << std::endl; * attr2 = sb::Attributes(std::vector{5.5, 6.6, 7.7, 8.8}); * attr2.add(glm::vec2{9.9f, 10.10f}); * std::cout << attr2 << std::endl; * * This prints * * warning: was not added to the attributes because its type is incompatible * { 420 69 9000 1 } * warning: { 0 0 0 0 } was not added to the attributes because its type is incompatible * { {1, 1, 2, 3} {5, 8, 13, 21} {34, 55, 89, 144} {5, 8, 13, 21} {34, 55, 89, 144} } * { 1.1 2 3 4 } * { {1, 1, 2, 3} {5, 8, 13, 21} {34, 55, 89, 144} {5, 8, 13, 21} {34, 55, 89, 144} } * * warning: { {1, 11} } was not added to the attributes because its type is incompatible * { {-5, 55, -555} {8, 88, 888} {9, 9, 9} {1000, 10000, 100000} } * warning: { {9.9, 10.1} } was not added to the attributes because its type is incompatible * { 5.5 6.6 7.7 8.8 } */ #pragma once #include #include #include #include #include #include #include #include "glm/glm.hpp" #include "gl.h" #include "Log.hpp" #include "extension.hpp" namespace sb { class Attributes; std::ostream& operator<<(std::ostream&, const Attributes&); class Attributes { private: /* Memory offset in the VBO */ GLintptr _offset = 0; /* Every type of vertex in the glm vertex types (bool, unsigned int, int, float) from 1D to 4D is included in this variant. * Each variant alternative is a vector of vertex type, so only homogenous vectors can be used. Index 0 is the std::monostate * alternative, which is used here for default initialization to indicate Attributes are in an empty state where no variant * alternative is selected. 1D vertices are specified by vectors of scalars, rather than the 1D glm vertex types. 1D vertices * also include an unsigned byte type. The std::vector alternative is not included because it is a special case of * std::vector that doesn't have a data member function. */ using Vertices = std::variant< std::monostate, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector>; Vertices vertices; /* Index is stored as a variant because it needs to be checked whether it is uninitialized (std::monostate) or not. */ std::variant _index; public: /*! * The default constructor creates an empty set of attributes. An empty set of attributes is indicated by * the class's vertex variant having the std::monostate variant. */ Attributes() {}; /*! * Construct a new Attributes object with vertices set to the vertices contained in this vector. * * @param vertices vector of vertices */ template Attributes(const std::vector& vertices) : vertices(vertices) {} /*! * Add a vertex. The vertex can be any of the variant types. * * @param vertex vertex of 1 to 4 dimensions */ template Attributes(const Type& vertex) : Attributes({vertex}) {} /*! * Add a vertex by specifying each coordinate as a separate argument. All arguments must have identical type. * * @param coordinate_0 x coordinate * @param coordinates y, z, and w coordinates can be specified as well, each as their own argument, using a parameter pack */ template), std::nullptr_t> = nullptr> Attributes(const XType& coordinate_0, const CoordinateTypes&... coordinates) : Attributes({std::initializer_list({coordinate_0, coordinates ...})}) {} /*! * Add vertices by passing an uninitialized list of vertices. This template applies to a list of * vertices where the list type is undeclared but the containing vertices have been initialized * or the type can be inferred because the vertices are 1D scalars. * * @param vertices uninitialized list of initialized multidimensional vertices or uninitialized scalars */ template Attributes(const std::initializer_list& vertices) : Attributes(std::vector(vertices.begin(), vertices.end())) {} /*! * Add vertices by passing a two-dimensional initializer list, a list of uninitialized vertices. * The appropriate glm vertex size is applied by looking at the length of the first uninitialized * vertex in the initializer list. * * @param vertices uninitialized list of uninitialized vertices */ template Attributes(const std::initializer_list>& vertices) { for (auto& vertex : vertices) { const CoordinateType* coordinate = vertex.begin(); if (vertex.size() == 1) { add(vertex); } else if (vertex.size() == 2) { add(glm::vec<2, CoordinateType, glm::defaultp>({*coordinate++, *coordinate})); } else if (vertex.size() == 3) { add(glm::vec<3, CoordinateType, glm::defaultp>({*coordinate++, *coordinate++, *coordinate})); } else if (vertex.size() == 4) { add(glm::vec<4, CoordinateType, glm::defaultp>({*coordinate++, *coordinate++, *coordinate++, *coordinate})); } } } /*! * Extend to include these attributes at the end. The argument will be automatically converted to an * Attributes object, so the vertices can be in any of the Attributes constructor formats. If no attributes * have been constructed or initialized, the current vertices will be set to these vertices. Otherwise, * vertices will be inserted at the end of the current vertices. * * @param other attributes to add */ void add(const Attributes& other); /*! * Add a 2D, 3D or 4D vertex by specifying each coordinate as a separate argument. All arguments must have * identical type. * * @param coordinate_0 x-coordinate * @param coordinate_1 y-coordinate * @param coordinate_2 z-coordinate (optional) * @param coordinate_3 w-coordinate (optional) */ template && (... && std::is_same_v), std::nullptr_t> = nullptr> void add(const XType& coordinate_0, const YType& coordinate_1, const CoordinateTypes&... coordinates) { /* The arguments are converted to an initializer list of initializer lists, a 2D initializer list with * the new vertex as its only element, and passed to the overloaded add function where the vertex is * converted into an Attributes object */ add({std::initializer_list({coordinate_0, coordinate_1, coordinates ...})}); } /*! * Return a const reference to the vertex at the specified index in the attributes vector. * * @param index vertex index * @return std::vector of type VertexType */ template const VertexType& read(std::size_t index) const { return std::get>(vertices)[index]; } /*! * 2D lookup of a value in the attributes. Return a const reference to the coordinate value at inner index * within a vertex at outer index in the attributes vector. * * @param outer vertex index * @param inner coordinate index within vertex * @return value of type CoordinateType */ template const CoordinateType& read(std::size_t outer, std::size_t inner) { return std::get>(vertices)[outer][inner]; } /*! * Return the attributes as a reference to a typed vector. If the type is not the alternative in use by the * attributes, std::bad_variant_access will be thrown. * * @return std::vector of type VertexType */ template operator const std::vector&() const { return std::get>(vertices); } /*! * Repeatedly add attributes to the end count number of times. If count is 1, this has the same effect as the * add function. * * @param other attributes to add repeatedly * @param count number of times to repeat */ void extend(const Attributes& other, std::size_t count = 1); /*! * Set the generic vertex attribute index for the attributes. * * This must be the same index that is returned when `glGetAttribLocation` is given the shader program and associated * variable name for the attributes in the shader program. * * To make sure those align, either assign an arbitray index with `glBindAttribLocation` and pass it to this function, or * skip this function and look up the index automatically at bind time by passing the location name and linked shader program * index to sb::Attributes::bind(const std::string&, GLuint). * * The index may also be specified to GL in the shader source code using the location layout qualifier in GLSL 3.3 and above. * * If assigning an arbitrary index, this can be any integer not greater than `GL_MAX_VERTEX_ATTRIBS`, and it must also be * passed to `glBindAttribLocation`. * * Once assigned, the index can be passed to `glVertexAttribPointer` to set the properties of the attributes on the GPU, either * manually or automatically through sb::VBO::add(sb::Attributes&). * * @param index index the attributes bind to in the shader program */ void index(GLint index); /*! * Get the generic vertex attribute index for the attributes. This index can be passed to `glBindAttribLocation` to assign the * index to a shader input variable if the index isn't already assigned by a layout qualifier in the GLSL source code. The * index should be passed to `glVertexAttribPointer` to specify the location to use when rendering. * * @return index the attributes bind to in the shader program */ GLint index() const; /*! * Set the byte offset into the VBO where this object's vertex data is stored. This can be set by passing this object to * `VBO::add`. * * @param offset byte offset in the VBO where this object's vertex data is stored */ void offset(GLintptr offset); /*! * Create a pointer to these attributes in the VAO using this object's index and offset values by calling `glVertexAttribPointer`. * * The offset should have been previously set by a call to VBO::add(sb::Attributes&) or some equivalent in manual GL calls. * The index should have been previously set to the index of the location in the shader program associated with the attributes * using sb::Attributes::index(GLint) or some equivalent in manual GL calls. */ void bind() const; /*! * Get the index of the uniform with the given name, store it as this object's index, and create a pointer to these * attributes in the VAO using this object's offset value. * * The offset should have been previously set by a call to VBO::add(sb::Attributes&) or some equivalent in manual GL calls. * The program must already be linked to get the location. * * @param name uniform name * @param program GLSL program, created with glCreateProgram */ void bind(const std::string& name, GLuint program); /*! * Enable the attributes in the VAO associated with this object's index. `Attributes::bind` must have been called previously, * unless the index has been set up manually with direct calls to GL. * * The object's current index is used refer to the pointer. */ void enable() const; /*! * Disable the attributes in the VAO associated with this object's index. */ void disable() const; /*! * Get the size in bytes of the object's underlying vector of vertices. This can be passed to OpenGL along with the memory * pointer when copying vertices to the GPU. It can also be used with sb::VBO::allocate(GLsizeiptr, GLenum). * * @return size in bytes of the attributes */ std::size_t size() const; /*! * @return the number of attributes in the object */ std::size_t count() const; /*! * Get the `GLenum` that corresponds to the type of scalar being held in the vertices. This will return either `GL_FLOAT`, * `GL_INT`, `GL_UNSIGNED_INT`, `GL_UNSIGNED_BYTE`, `GL_BOOL`, or `GL_INVALID_ENUM`. A return value of `GL_INVALID_ENUM` * indicates an error meaning there are no attributes and no type exists yet. * * @return the attributes type */ GLenum type() const; /*! * Get the number of dimensions in the vertices. If there are no vertices, return 0. * * @return number of dimensions in the vertices */ std::size_t dimensions() const; /*! * Normalization isn't supported, so this always returns `false` * * @return false */ bool normalized() const; /*! * Overload the stream operator to support attributes. Add a string representation of the vertices to the output stream. Since * this is defined as a friend function and isn't in the global scope, it should prevent it being looked up with arguments other * than attributes. * * @param out output stream * @param attributes attributes object to print * @return edited output stream */ friend std::ostream& operator<<(std::ostream& out, const Attributes& attributes); /*! * Get a pointer to the first vertex in the object's underlying vector of vertices. This can be used along with the size * function to pass the raw bytes of the vertices to the GPU. * * @return pointer to the first vertex in the object's underlying vector of vertices */ operator const void*() const; /*! * Attributes can be represented by their GL index when an int is requested. * * @return index the attributes bind to in the shader program */ operator int() const; }; }