diff --git a/src/Attributes.cpp b/src/Attributes.cpp index c0c58c5..9595f9d 100644 --- a/src/Attributes.cpp +++ b/src/Attributes.cpp @@ -10,32 +10,25 @@ #include "Attributes.hpp" -/* Set the generic vertex attribute index for the attributes. It must be the same index that is - * returned when glGetAttribLocation is given the shader program and variable name the attributes - * will be associated with. The bind class member functions can automatically do that, and they - * should be used instead of this function if possible. - * - * The index may be specified to GL in the shader source code using the location layout qualifier - * in GLSL 3.3 and above. It can also be set automatically by GL when a shader program is linked. - * In both cases, the return value of glGetAttribLocation should be passed to this function. - * Otherwise, this index can be an arbitrary choice that is not greater than GL_MAX_VERTEX_ATTRIBS, - * in which case it would be explicitly 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 the add member function in the - * VBO class. */ void sb::Attributes::index(GLint index) { - attribute_index = index; + _index = index; } -/* Returns 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. */ GLint sb::Attributes::index() const { - return attribute_index; + try + { + return std::get(_index); + } + catch (const std::bad_variant_access&) + { + std::ostringstream message; + message << "Trying to get index for " << dimensions() << "D " << size() << "b attributes, but no index has been set yet. Assign an" + << " index with glBindAttribLocation and copy it to Attributes::index(GLint) or look up the index automatically at bind time by" + << " passing the location name and linked shader program index to Attributes::bind(const std::string&, GLuint)."; + throw std::runtime_error(message.str()); + } } void sb::Attributes::offset(GLintptr offset) @@ -43,7 +36,6 @@ void sb::Attributes::offset(GLintptr offset) _offset = offset; } -/* Returns the count of attributes */ std::size_t sb::Attributes::count() const { return std::visit([] (const auto& vector) -> std::size_t { @@ -59,8 +51,6 @@ std::size_t sb::Attributes::count() const }, vertices); } -/* Returns 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. */ std::size_t sb::Attributes::size() const { return std::visit([] (const auto& vector) -> std::size_t { @@ -76,6 +66,17 @@ std::size_t sb::Attributes::size() const }, vertices); } +void sb::Attributes::bind() const +{ + /* Define an array of vertex attributes that have data stored at the specified offset. */ + glVertexAttribPointer(index(), dimensions(), type(), normalized(), 0, reinterpret_cast(_offset)); + + /* Debug */ + std::ostringstream message; + message << "After binding " << *this; + sb::Log::gl_errors(message.str()); +} + void sb::Attributes::bind(const std::string& name, GLuint program) { GLint index = glGetAttribLocation(program, name.c_str()); @@ -96,13 +97,8 @@ void sb::Attributes::bind(const std::string& name, GLuint program) message << "After getting attribute location of " << name; sb::Log::gl_errors(message.str()); - /* Define an array of vertex attributes that have data stored at the specified offset. */ - glVertexAttribPointer(this->index(), dimensions(), type(), normalized(), 0, reinterpret_cast(_offset)); - - /* Debug */ - message = std::ostringstream(); - message << "After binding " << *this << " to " << name; - sb::Log::gl_errors(message.str()); + /* Call glVertexAttribPointer with the index */ + bind(); } void sb::Attributes::enable() const @@ -115,10 +111,6 @@ void sb::Attributes::disable() const glDisableVertexAttribArray(*this); } -/* Return 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. */ GLenum sb::Attributes::type() const { return std::visit([] (const auto& vector) -> GLenum { @@ -159,7 +151,6 @@ GLenum sb::Attributes::type() const }, vertices); } -/* Return the number of dimensions in the vertices. If there are no vertices, return 0. */ std::size_t sb::Attributes::dimensions() const { return std::visit([] (const auto& vector) -> std::size_t { @@ -183,14 +174,11 @@ std::size_t sb::Attributes::dimensions() const }, vertices); } -/* Normalization isn't supported, so this always returns false */ bool sb::Attributes::normalized() const { return false; } -/* Returns 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. */ sb::Attributes::operator const void*() const { return std::visit([] (const auto& vector) -> const void* { @@ -206,7 +194,6 @@ sb::Attributes::operator const void*() const }, vertices); } -/* Attributes can be represented by their GL index when an int is requested. */ sb::Attributes::operator int() const { return index(); @@ -248,7 +235,7 @@ void sb::Attributes::add(const Attributes& other) if (!found) { std::ostringstream message; - message << "warning: " << other << " was not added to the attributes because its type is incompatible"; + message << "warning: " << other << " was not added to " << *this << " because types are incompatible"; sb::Log::log(message); } } @@ -262,13 +249,9 @@ void sb::Attributes::extend(const Attributes& other, std::size_t count) } } -/* 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. */ std::ostream& sb::operator<<(std::ostream& out, const Attributes& attributes) { - out << ", std::monostate>) { diff --git a/src/Attributes.hpp b/src/Attributes.hpp index a48d1a3..9a5be14 100644 --- a/src/Attributes.hpp +++ b/src/Attributes.hpp @@ -1,4 +1,4 @@ -/*!
+/*!
  *        /\         +------------------------------------------------------+
  *   ____/  \____   /| - Open source game framework licensed to freely use, |
  *   \          /  / |   copy, modify and sell without restriction          |
@@ -7,7 +7,7 @@
  * | ~~~~~~~~~~~~ |  +------------------------------------------------------+
  * | SPACE ~~~~~  | /
  * |  ~~~~~~~ BOX |/
- * +--------------+   
+ * +--------------+ * * Attributes * ========== @@ -102,10 +102,11 @@ #include #include #include +#include #include "glm/glm.hpp" #include "Log.hpp" #include "extension.hpp" - + namespace sb { @@ -118,6 +119,9 @@ namespace sb 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 @@ -130,8 +134,9 @@ namespace sb std::vector, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector, std::vector>; Vertices vertices; - GLint attribute_index = 0; - GLintptr _offset = 0; + + /* Index is stored as a variant because it needs to be checked whether it is uninitialized (std::monostate) or not. */ + std::variant _index; public: @@ -142,19 +147,26 @@ namespace sb Attributes() {}; /*! - * Construct a new Attributes object with vertices set to the vertices contained in this vector + * 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 idential type. + * 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> @@ -165,6 +177,8 @@ namespace sb * 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) : @@ -174,6 +188,8 @@ namespace sb * 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) @@ -277,7 +293,35 @@ namespace sb */ void extend(const Attributes& other, std::size_t count = 1); - void index(GLint); + /*! + * 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; /*! @@ -288,12 +332,21 @@ namespace sb */ 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` or some equivalent in manual GL calls. The program - * must already be linked to get the location. + * 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 @@ -313,13 +366,66 @@ namespace sb */ 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; - friend std::ostream& operator<<(std::ostream&, const Attributes&); + + /*! + * 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; }; diff --git a/src/Model.cpp b/src/Model.cpp index 94251df..bd096dc 100644 --- a/src/Model.cpp +++ b/src/Model.cpp @@ -77,8 +77,17 @@ std::shared_ptr& sb::Model::operator[](const std::string& name) void sb::Model::enable() const { for (const auto& attributes : this->attributes()) - { - attributes.second->enable(); + { + try + { + attributes.second->enable(); + } + catch (const std::runtime_error& error) + { + std::ostringstream message; + message << "Error enabling " << attributes.first << " attributes on model"; + throw std::runtime_error(message.str()); + } } } @@ -136,7 +145,30 @@ void sb::Model::load() texture.load(); } } - + +void sb::Model::add(sb::VBO& vbo) +{ + for (auto& [name, attributes] : attributes()) + { + vbo.add(*attributes); + } +} + +void sb::Model::bind() +{ + /* Bind textures */ + for (sb::Texture& texture : textures()) + { + texture.bind(); + } + + /* Bind attributes */ + for (auto& [name, attributes] : attributes()) + { + attributes->bind(); + } +} + const glm::mat4& sb::Model::transformation() const { return _transformation; diff --git a/src/Model.hpp b/src/Model.hpp index 4c32665..7042240 100644 --- a/src/Model.hpp +++ b/src/Model.hpp @@ -30,6 +30,7 @@ #include "Attributes.hpp" #include "Texture.hpp" #include "Carousel.hpp" +#include "VBO.hpp" namespace sb { @@ -113,6 +114,8 @@ namespace sb * Assign name to attributes, copy and wrap in a shared pointer. The model can share ownership of the created attribute * memory with callers that request it. * + * Any existing attributes with the same name will be replaced with the given attributes. + * * @param attributes attributes object to copy and wrap * @param name name the model will associate with the attributes */ @@ -121,6 +124,8 @@ namespace sb /*! * Assign name to attributes (for example, "position" or "uv") and share ownership. * + * Any existing attributes with the same name will be replaced with the given attributes. + * * @param attributes attributes object to share with this model * @param name name the model will associate with the attributes */ @@ -130,7 +135,15 @@ namespace sb * Get the attributes under name, wrapped in the shared pointer held by this object. * * This function uses operator[] or std::map, so this can be used to add new attributes to the object if they are - * wrapped in a shared pointer. + * wrapped in a shared pointer or the return value is deferenced. + * + * sb::Plane plane; + * sb::Attributes color_attributes; + * color_attributes.extend({255.0f, 0.0f, 0.0f, 255.0f}, plane["position"]->count()); + * plane["color"] = std::make_shared(color_attributes); + * // *plane["color"] = color_attributes; // same effect + * // plane.attributes(color_attributes, "color"); // same effect + * sprite = sb::Sprite(plane); * * @param name name of the attributes to get * @return writable reference to the attributes at name @@ -188,6 +201,22 @@ namespace sb */ void load(); + /*! + * Add all attributes to the given vertex buffer object. The buffer object should have been previously allocated to at least the + * size of this model by passing Model::size() to VBO::allocate(GLsizeiptr, GLenum). + * + * The VBO must currently be bound. + * + * @param vbo vertex buffer object that the model's attribute vertices will be added to + */ + void add(sb::VBO& vbo); + + /*! + * Bind all of this model's attributes and textures by calling each of their bind methods. Textures and attributes all must already + * have GL indices set, for example by calling Texture::generate() and Attributes::index(GLint) on each. + */ + void bind(); + /*! * @return a constanst reference to the model's transformation matrix */ diff --git a/src/Sprite.hpp b/src/Sprite.hpp index 9c825b7..d89aee6 100644 --- a/src/Sprite.hpp +++ b/src/Sprite.hpp @@ -164,6 +164,28 @@ namespace sb plane.load(); } + /*! + * Add all attributes to the given vertex buffer object. The buffer object should have been previously allocated to at least the + * size of this sprite by passing Sprite::size() to VBO::allocate(GLsizeiptr, GLenum). + * + * The VBO must currently be bound. + * + * @param vbo vertex buffer object that the sprite's attribute vertices will be added to + */ + void add(sb::VBO& vbo) + { + plane.add(vbo); + } + + /*! + * Bind all of this sprite's attributes and textures by calling each of their bind methods. Textures and attributes all must already + * have GL indices set, for example by calling Texture::generate() and Attributes::index(GLint) on each. + */ + void bind() + { + plane.bind(); + } + /*! * Get a reference to the plane object's shared pointer to the attributes with the given name. The underlying attributes * object is fully exposed, meaning it can be edited, and both its const and non-const methods can be used. @@ -258,6 +280,8 @@ namespace sb * this sprite, and false if not. The currently bound shader should be written to use that flag. For example, it could use the * flag to choose whether to use the UV or the color attributes. * + * The vertex data is expected to be bound before this function is called. + * * @param transformation_uniform transformation uniform ID * @param view the view matrix for transforming from world space to camera space * @param projection projection matrix for transforming from camera space to clip space diff --git a/src/VBO.hpp b/src/VBO.hpp index c68cce9..1abe859 100644 --- a/src/VBO.hpp +++ b/src/VBO.hpp @@ -67,20 +67,22 @@ namespace sb * GL_DYNAMIC_READ, or GL_DYNAMIC_COPY. If using `glBufferSubData` directly to change the VBO's data frequently, a value other than the * default GL_STATIC_DRAW can be used. * + * The VBO must currently be bound. + * * @param size number of bytes of GPU memory to allocate to the buffer * @param usage indicator to the GL implementation of how the data will be used, see `glBufferData` for info */ void allocate(GLsizeiptr size, GLenum usage = GL_STATIC_DRAW); /*! - * Set memory in the GPU buffer to the values of the attribute data using `glBufferSubData`. The memory - * is a contiguous area from the object's current byte offset value to the offset plus the size in - * bytes of the attributes. After the memory is set, the offset is updated to point to the end of the - * area. + * Set memory in the GPU buffer to the values of the attribute data using `glBufferSubData`. The memory is a contiguous area from the + * object's current byte offset value to the offset plus the size in bytes of the attributes. After the memory is set, the offset is + * updated to point to the end of the area. * - * The offset is stored in the sb::Attributes object, so the object can associate the vertex data with - * the VAO by passing the offset along when it calls glVertexAttribPointer in its sb::Attributes::bind - * method. + * The offset replaces the currently set offset in the sb::Attributes object, so the attributes object can associate its vertex data with + * the VAO by passing the offset along when it calls `glVertexAttribPointer` in its sb::Attributes::bind() method. + * + * The VBO must currently be bound. * * @warning This function is expected to change in the future to allow more control over the offset so * the VBO can be refilled using this class.