attribute index exception, overload attributes bind, model add and bind methods

This commit is contained in:
ohsqueezy 2023-07-18 23:19:57 -04:00
parent d1fe6b927c
commit cd66f70d0c
6 changed files with 243 additions and 67 deletions

View File

@ -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<GLint>(_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<GLvoid*>(_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<GLvoid*>(_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 << "<Attributes #" << attributes.index() << ", " << attributes.dimensions() << "D, " <<
attributes.size() << " bytes, ";
out << "<Attributes " << attributes.dimensions() << "D, " << attributes.size() << " bytes, ";
std::visit([&] (const auto& vector) {
if constexpr (!std::is_same_v<std::decay_t<decltype(vector)>, std::monostate>)
{

View File

@ -1,4 +1,4 @@
/*!<pre>
/*!
* /\ +------------------------------------------------------+
* ____/ \____ /| - Open source game framework licensed to freely use, |
* \ / / | copy, modify and sell without restriction |
@ -7,7 +7,7 @@
* | ~~~~~~~~~~~~ | +------------------------------------------------------+
* | SPACE ~~~~~ | /
* | ~~~~~~~ BOX |/
* +--------------+ </pre>
* +--------------+
*
* Attributes
* ==========
@ -102,10 +102,11 @@
#include <initializer_list>
#include <type_traits>
#include <utility>
#include <exception>
#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<glm::bvec3>, std::vector<glm::uvec3>, std::vector<glm::ivec3>, std::vector<glm::vec3>,
std::vector<glm::bvec4>, std::vector<glm::uvec4>, std::vector<glm::ivec4>, std::vector<glm::vec4>>;
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<std::monostate, GLint> _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<typename Type>
Attributes(const std::vector<Type>& vertices) : vertices(vertices) {}
/*!
* Add a vertex. The vertex can be any of the variant types.
*
* @param vertex vertex of 1 to 4 dimensions
*/
template<typename Type>
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<typename XType, typename... CoordinateTypes,
std::enable_if_t<(... && std::is_same_v<CoordinateTypes, XType>), 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<typename VertexType>
Attributes(const std::initializer_list<VertexType>& 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<typename CoordinateType>
Attributes(const std::initializer_list<std::initializer_list<CoordinateType>>& 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;
};

View File

@ -77,8 +77,17 @@ std::shared_ptr<sb::Attributes>& 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;

View File

@ -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<sb::Attributes>(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
*/

View File

@ -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

View File

@ -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.