427 lines
19 KiB
C++
427 lines
19 KiB
C++
/*!
|
|
* /\ +------------------------------------------------------+
|
|
* ____/ \____ /| - Open source game framework licensed to freely use, |
|
|
* \ / / | copy, modify and sell without restriction |
|
|
* +--\ ^__^ /--+ | |
|
|
* | ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
|
* | ~~~~~~~~~~~~ | +------------------------------------------------------+
|
|
* | 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<std::uint32_t>{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<float>{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 <ostream>
|
|
#include <vector>
|
|
#include <variant>
|
|
#include <initializer_list>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <exception>
|
|
|
|
#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<bool> 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::uint8_t>, std::vector<std::uint32_t>, std::vector<std::int32_t>, std::vector<float>,
|
|
std::vector<glm::bvec2>, std::vector<glm::uvec2>, std::vector<glm::ivec2>, std::vector<glm::vec2>,
|
|
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;
|
|
|
|
/* 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:
|
|
|
|
/*!
|
|
* 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<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 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>
|
|
Attributes(const XType& coordinate_0, const CoordinateTypes&... coordinates) :
|
|
Attributes({std::initializer_list<XType>({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<typename VertexType>
|
|
Attributes(const std::initializer_list<VertexType>& vertices) :
|
|
Attributes(std::vector<VertexType>(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<typename CoordinateType>
|
|
Attributes(const std::initializer_list<std::initializer_list<CoordinateType>>& 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<typename XType, typename YType, typename... CoordinateTypes,
|
|
std::enable_if_t<std::is_same_v<XType, YType> && (... && std::is_same_v<CoordinateTypes, XType>),
|
|
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<XType>({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<typename VertexType>
|
|
const VertexType& read(std::size_t index) const
|
|
{
|
|
return std::get<std::vector<VertexType>>(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<typename VertexType, typename CoordinateType = float>
|
|
const CoordinateType& read(std::size_t outer, std::size_t inner)
|
|
{
|
|
return std::get<std::vector<VertexType>>(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<typename VertexType>
|
|
operator const std::vector<VertexType>&() const
|
|
{
|
|
return std::get<std::vector<VertexType>>(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;
|
|
|
|
};
|
|
|
|
}
|