# [`gltraits`][]
A [C++11][](/[C++14][])/[OpenGL][]>=[1.0][](/[GLM][]) [trait][]s library.
This library seeks to unify some parts of the OpenGL [API][] to ease [generic
programming][]. It also provides sensible default arguments, [optional][debug]
[check][]s for [version][]/[extension][] support, and optional support for
[OpenGL Mathematics (GLM)][GLM] (which provides [GLSL][]-like types).
A more philosophical description: it aims to make the implicit symmetries of
the OpenGL API explicit. In the process, some wrinkles in the symmetry are also
brought to light. Therefore, it may be useful for learning and understanding
the OpenGL API (although this use case may be limited due to the (ab)use of C++
language features).
This header-only library makes heavy use of macros. For easy inspection of the
results, a script that runs the preprocessor is available in
[`doc/preprocess`][] and its output in [`doc/preprocess.hpp`][].
A typical use case is demonstrated by this quote from [OGLDEV on YouTube][]:
> Notice, in order to make this an unsigned integer texture we use `GL_RGB32UI`
> as the `internal_format`, `GL_RGB_INTEGER` as the `format`, and
> `GL_UNSIGNED_INT` as the data `type`. Combining all these formats and types
> correctly in OpenGL can often be a pain, and I literally pulled the last few
> pieces of hair from my head trying to get this to work.
These constants are provided in `GLTraits::Value<glm::uvec3>`. Of course, this
becomes even more valuable when `glm::uvec3` is replaced by some unknown
template parameter.
The function call seen in the video
```cpp
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32UI, WindowWidth, WindowHeight, 0, GL_RGB_INTEGER, GL_UNSIGNED_INT, NULL);
```
can be replaced with
```cpp
GLTraits::Texture<2>::tex_image<glm::uvec3>(GL_TEXTURE_2D, {WindowWidth, WindowHeight});
```
It is recommended to use a library based on `gltraits` that abstracts this
further.
[`gltraits`]: https://git.rcrnstn.net/rcrnstn/gltraits
[C++11]: https://en.wikipedia.org/wiki/C++11
[C++14]: https://en.wikipedia.org/wiki/C++14
[OpenGL]: https://en.wikipedia.org/wiki/OpenGL
[1.0]: https://en.wikipedia.org/wiki/OpenGL#Version_history
[GLM]: https://glm.g-truc.net
[trait]: https://en.wikipedia.org/wiki/Trait_(computer_programming)
[API]: https://en.wikipedia.org/wiki/API
[generic programming]: https://en.wikipedia.org/wiki/Generic_programming
[debug]: https://git.rcrnstn.net/rcrnstn/glbase#debug
[check]: https://git.rcrnstn.net/rcrnstn/glbase#check
[version]: https://en.wikipedia.org/wiki/OpenGL#Version_history
[extension]: https://www.khronos.org/opengl/wiki/OpenGL_Extension
[GLSL]: https://www.khronos.org/opengl/wiki/OpenGL_Shading_Language
[`doc/preprocess`]: doc/preprocess
[`doc/preprocess.hpp`]: doc/preprocess.hpp
[OGLDEV on YouTube]: https://www.youtube.com/watch?v=71G-PVpaVk8&t=5m17s
## Usage
### Value
The [empty][] `struct GLTraits::Value<typename Value>` is template specialized
on the following types.
- `GLfloat`
- `bool`
- `GL{,u}byte`
- `GL{,u}short`
- `GL{,u}int`
- `GLdouble`
[GLM][] support is enabled if `glm/glm.hpp` is included (specifically, if
`GLM_VERSION` is defined) before inclusion of `gltraits.hpp`. In that case
`GLTraits::Value` is additionally template specialized on the following types.
- `glm::{,i,u,d}vec{2,3,4}`
- `glm::{,d}mat{2{,x3,x4},3{x2,,x4},4{x2,x3,}}`
`GLTraits::Value` contains the following `static constexpr` member variables.
- `char const name[]`
- `GLint columns`
- `GLint rows`
- `GLenum glsl`
- `GLenum format`
- `GLenum type`
- `GLenum internal_format`
- `GLenum internal_format_srgb`
- `GLenum internal_format_compressed`
- `GLenum internal_format_compressed_srgb`
- `bool integer`
- `GLenum id`
`id` is guaranteed to be unique to this `Value` and is equal to `glsl` for all
`Value`s except `GL{,u}{byte,short}`, for which it is equal to `type`.
`GLTraits::Value` contains the following `static` member functions.
- ```cpp
void uniform(GLint location, Value const & value)
```
Generalization of `glUniform*`.
Uploads `value` to the [uniform][] indicated by `location` of the current
[shader][] program.
- ```cpp
void vertex_attrib(GLint location, Value const & value)
```
Generalization of `glVertexAttrib*`.
Uploads `value` to the [non-array attribute][] indicated by `location`.
Note that [`glDisableVertexAttribArray`][] is not called (for performance).
- ```cpp
void vertex_attrib_pointer(
GLint location,
std::size_t offset = 0,
std::size_t stride = sizeof(Value)
)
```
Generalization of `glVertexAttrib*Pointer`.
Sets the [format][] as well as the [offset and stride][] of the [array
attribute][] indicated by `location`. Note that
[`glEnableVertexAttribArray`][] is not called (for performance).
If `location` is `-1`, the above calls will do nothing. No error will be
generated in this case.
Note that matrix types (e.g. from [GLM][], if enabled), occupy several
consecutive attribute locations (one per column), which are all handled
automatically by `vertex_attrib{,_pointer}(...)` above.
`GLTRAITS_VALUE*` macros are defined to ease the definition of new template
specializations of `GLTraits::Value`. Consult the source for the definitions
and usage examples.
The [empty][] `struct GLTraits::ValueID<GLenum id>` is template specialized on
the different values of `GLTraits::Value<Value>::id`.
`GLTraits::ValueID` contains the following type definitions.
- `Value`. An alias for the template type parameter for which
`GLTraits::Value<Value>::id == id` holds.
`GLTraits::ValueID` provides a compile time mapping from `GLenum id` back to
the type `Value`. E.g. `GLTraits::ValueID<GL_FLOAT>::Value` is an alias for
`GLfloat`. This works for all supported types, including those provided by
[GLM][] if enabled, e.g. `GLTraits::ValueID<GL_FLOAT_VEC3>::Value` would be an
alias for `glm::vec3`.
[empty]: https://en.cppreference.com/w/cpp/types/is_empty
[uniform]: https://www.khronos.org/opengl/wiki/Uniform_(GLSL)
[shader]: https://www.khronos.org/opengl/wiki/Shader
[non-array attribute]: https://www.khronos.org/opengl/wiki/Vertex_Specification#Non-array_attribute_values
[format]: https://www.khronos.org/opengl/wiki/Vertex_Specification#Vertex_format
[offset and stride]: https://www.khronos.org/opengl/wiki/Vertex_Specification#Vertex_buffer_offset_and_stride
[array attribute]: https://www.khronos.org/opengl/wiki/Vertex_Specification#Vertex_Buffer_Object
[`glDisableVertexAttribArray`]: https://registry.khronos.org/OpenGL-Refpages/gl4/html/glEnableVertexAttribArray.xhtml
[`glEnableVertexAttribArray`]: https://registry.khronos.org/OpenGL-Refpages/gl4/html/glEnableVertexAttribArray.xhtml
### Object
The [empty][] `struct GLTraits::Object<GLenum object_type>` is template
specialized on the following values.
- `GL_TEXTURE`
- `GL_BUFFER`
- `GL_QUERY`
- `GL_PROGRAM`
- `GL_SHADER`
- `GL_VERTEX_ARRAY`
- `GL_FRAMEBUFFER`
- `GL_RENDERBUFFER`
- `GL_SAMPLER`
- `GL_TRANSFORM_FEEDBACK`
- `GL_PROGRAM_PIPELINE`
`GLTraits::Object` contains the following `static constexpr` member variables.
- `char const name[]`
`GLTraits::Object` contains the following `static` member functions.
- ```cpp
template<typename... Args>
void gen_objects(GLsizei n, GLuint * objects, Args... args)
```
Generalization of `glGen*s` and `glCreate`.
- ```cpp
void delete_objects(GLsizei n, GLuint * objects)
```
Generalization of `glDelete*s` and `glDelete*`.
For `object_types` equal to `GL_SHADER`, `GL_PROGRAM`, and
`GL_PROGRAM_PIPELINE`, `GLTraits::Object` additionally contains the following
`static` member functions.
- ```cpp
std::string info_log(GLuint object)
```
Generalization of `glGet*InfoLog`.
`GLTRAITS_OBJECT*` macros are defined to ease the definition of new template
specializations of `GLTraits::Object`. Consult the source for the definitions
and usage examples.
### Texture
The [empty][] `struct GLTraits::Texture<std::size_t N>` is template specialized
on the following values.
- `1`. 1D textures.
- `2`. 2D textures.
- `3`. 3D textures.
`GLTraits::Texture` contains the following type definitions.
- `Size`. An alias for `std::array<GLsizei, N>`.
- `Offset`. An alias for `std::array<GLsizei, N>`.
- `CopySize`. An alias for `std::array<GLsizei, min(N, 2)>`.
- `CopyOffset`. An alias for `std::array<GLsizei, 2>`.
`GLTraits::Texture` contains the following `static constexpr` member variables.
- `default_target`. This is the default target, but many other targets can be
used with the member functions described below. E.g. for `N = 2`
`default_target` is `GL_TEXTURE_2D`, but valid `target` function arguments
are `GL_{,PROXY}_TEXTURE_{2D,RECTANGLE,1D_ARRAY}`,
`GL_TEXTURE_CUBE_MAP_{POSITIVE,NEGATIVE}_{X,Y,Z}`, and
`GL_PROXY_TEXTURE_CUBE_MAP`.
- `default_binding`. Likewise, this is the default binding.
`GLTraits::Texture` contains the following `static` member functions.
- ```cpp
template<typename Value = GLubyte>
void tex_image(
GLenum target,
Size size,
GLenum internal_format = 0,
Value const * data = nullptr,
GLenum format = 0,
GLenum type = 0,
GLint level = 0,
GLint border = 0
)
```
Generalization of `glTexImage{1,2,3}D`.
OpenGL requires that `format` matches the kind of [image format][]
specified by `internal_format` (even if `data` is `nullptr`!). Moreover, if
`internal_format` specifies an unsized image format, `type` may be used by
the implementation when choosing a size (perhaps even if that size is
slow!). Therefore, if `format` and/or `type` is `0` (the default) sensible
values will be chosen based on the given (or automatically chosen, see
below) `internal_format` (note that there is no requirement that the number
of color components of `internal_format` and `format` match):
- [Color formats][]: `GLTraits::Value<Value>` member variables.
- [Depth/stencil formats][]: `GL_DEPTH_STENCIL`, `GL_UNSIGNED_INT_24_8`.
- [Depth formats][]: `GL_DEPTH_COMPONENT`, `GL_UNSIGNED_INT`.
- [Stencil formats][]: `GL_STENCIL_INDEX`, `GL_UNSIGNED_BYTE`.
- ```cpp
template<typename Value = GLubyte>
void {tex,texture}_storage(
{GLenum,GLuint} {target,texture},
Size size,
GLenum internal_format = 0,
GLsizei levels = 1
)
```
Generalizations of `gl{Tex,Texture}Storage{1,2,3}D`, which both use
[immutable storage][], where the latter uses [Direct State Access][].
- ```cpp
template<typename Value = GLubyte>
void {tex,texture}_sub_image(
{GLenum,GLuint} {target,texture},
Size size,
Value const * data,
GLenum format = 0,
GLenum type = 0,
Offset offset = {},
GLint level = 0
)
```
Generalizations of `gl{Tex,Texture}SubImage{1,2,3}D`, where the latter uses
[Direct State Access][].
- ```cpp
void copy_{tex,texture}_sub_image(
{GLenum,GLuint} {target,texture},
CopySize copy_size,
CopyOffset copy_offset = {},
Offset offset = {},
GLint level = 0
)
```
Generalizations of `glCopy{Tex,Texture}SubImage{1,2,3}D`, where the latter
uses [Direct State Access][].
- ```cpp
void compressed_tex_image(
GLenum target,
Size size,
GLenum internal_format,
GLsizei data_size = 0,
void const * data = nullptr,
GLint level = 0,
GLint border = 0
)
```
Generalization of `glCompressedTexImage{1,2,3}D`.
- ```cpp
void compressed_{tex,texture}_sub_image(
{GLenum,GLuint} {target,texture},
Size size,
GLsizei data_size,
void const * data,
GLenum internal_format,
Offset offset = {},
GLint level = 0
)
```
Generalizations of `glCompressed{Tex,Texture}SubImage{1,2,3}D`, where the
latter uses [Direct State Access][].
For the member functions that are template parameterized on `Value`, when
`internal_format`, `format` and/or `type` is `0` (the default), the value used
will be taken from the corresponding `GLTraits::Value<Value>` member variable.
Note that when the `Value` template type parameter is deduced from `data` the
automatically chosen `format` may not be correct. E.g. it is reasonable for
`data` to be a `GLubyte` pointer to RGBA values in which case `format` must be
manually specified as `GL_RGBA` since the automatically chosen `format` would
be `GL_RED`.
Note that the [pixel transfer parameters][] are not modified, this must be
handled manually if required.
(There is no `copy_tex_image(...)` because OpenGL does not provide
`glCopyTexImage3D`, only `glCopyTexImage{1,2}D`.)
(There is no `{tex_image,tex_storage,texture_storage}_multisample(...)` because
OpenGL does not provide `gl{TexImage,TexStorage,TextureStorage}1DMultisample`,
only `gl{TexImage,TexStorage,TextureStorage}{2,3}DMultisample`.)
(There is no `compressed_texture_image(...)` because OpenGL does not provide
`glCompressedTextureImage{1,2,3}D`, only the `Sub` variants.)
(There is no `framebuffer_texture(...)` because OpenGL provides incompatible
signatures for `glFramebufferTexture1D` and `glFramebufferTexture{2,3}D`.)
[image format]: https://www.khronos.org/opengl/wiki/Image_Format
[color formats]: https://www.khronos.org/opengl/wiki/Image_Format#Color_formats
[depth/stencil formats]: https://www.khronos.org/opengl/wiki/Image_Format#Depth_stencil_formats
[depth formats]: https://www.khronos.org/opengl/wiki/Image_Format#Depth_formats
[stencil formats]: https://www.khronos.org/opengl/wiki/Image_Format#Stencil_only
[Direct State Access]: https://www.khronos.org/opengl/wiki/Direct_State_Access
[immutable storage]: https://www.khronos.org/opengl/wiki/Texture_Storage#Immutable_storage
[pixel transfer parameters]: https://www.khronos.org/opengl/wiki/Pixel_Transfer#Pixel_transfer_parameters
### Member
`GLTraits::members()` provides compile time reflection for types consisting
(only) of types supported by `GLTraits::Value`. This feature is only available
if [C++14][] or above is supported.
If `T` is a non-[empty][] [trivially copyable][] [standard-layout][] type and
there exists template specializations of `GLTraits::Value` for the types of
either
- public member variables of `T`, if `T` is a [class][], or
- elements of `T`, if `T` is an [array][], or
- `T` itself, if `T` is a [scalar][]
then `constexpr auto GLTraits::members<T>()` returns an instance of the
[empty][] `struct GLTraits::Members<typename T, typename... Members>` where
each of `Members...` is `GLTraits::Member<typename Value, std::size_t offset>`
that describe the ("member") types above, in the order they occur in `T`.
`Value` is the type of the member and `offset` is the offset of the member in
`T`. Note that `offset` is only correct if the padding in `T` is minimal while
still satisfying the [`alignof`][] of the member types. The author knows of no
compiler that breaks this assumption unless explicitly instructed (e.g. with
[`alignas`][], which are not taken into account), however the standard allows
it.
An example use case would be along the lines of:
```cpp
template<
typename T,
typename Value,
std::size_t offset,
typename... Members
>
void vertex_setup(
GLTraits::Members<T, GLTraits::Member<Value, offset>, Members...>,
GLint location = 0
)
{
GLTraits::Value<Value>::vertex_attrib_pointer(location, offset, sizeof(T));
vertex_setup(
GLTraits::Members<T, Members...>{},
location + GLTraits::Value<Value>::columns
);
}
template<typename T>
void vertex_setup(GLTraits::Members<T>, GLint)
{}
struct Vertex
{
glm::vec3 position;
glm::vec2 tex_coord;
glm::mat3 tbn;
glm::ivec4 bone_indices;
glm::vec4 bone_weights;
GLubyte flags;
GLdouble unaligned;
};
vertex_setup(GLTraits::members<Vertex>());
```
This use case is tested in [`tests/vertex_setup.cpp`][]. To verify that the
compiler sees through all the templates, a script that builds it and
disassembles it available in [`doc/vertex_setup`][] and its output in
[`doc/vertex_setup.i`][].
It is recommended to use a library based on `gltraits` that abstracts this
further.
Many thanks to Antony Polukhin (the author of [Boost.PFR][] and its standalone
version [`magic_get`][]) on whose talks this implementation is based:
- [CppCon 2016: C++14 Reflections Without Macros, Markup nor External Tooling][]
- [Meeting C++ 2018: Better C++14 reflections][]
[trivially copyable]: https://en.cppreference.com/w/cpp/types/is_trivially_copyable
[standard-layout]: https://en.cppreference.com/w/cpp/types/is_standard_layout
[class]: https://en.cppreference.com/w/cpp/types/is_class
[array]: https://en.cppreference.com/w/cpp/types/is_array
[scalar]: https://en.cppreference.com/w/cpp/types/is_scalar
[`alignof`]: https://en.cppreference.com/w/cpp/language/alignof
[`alignas`]: https://en.cppreference.com/w/cpp/language/alignas
[`tests/vertex_setup.cpp`]: tests/vertex_setup.cpp
[`doc/vertex_setup`]: doc/vertex_setup
[`doc/vertex_setup.i`]: doc/vertex_setup.i
[Boost.PFR]: https://www.boost.org/libs/pfr
[`magic_get`]: https://github.com/apolukhin/magic_get
[CppCon 2016: C++14 Reflections Without Macros, Markup nor External Tooling]: https://www.youtube.com/watch?v=abdeAew3gmQ
[Meeting C++ 2018: Better C++14 reflections]: https://www.youtube.com/watch?v=UlNUNxLtBI0
## Building
See [`BUILDING.md`][].
[`BUILDING.md`]: BUILDING.md
## License
Licensed under the [ISC License][] unless otherwise noted, see the
[`LICENSE`][] file.
[ISC License]: https://choosealicense.com/licenses/isc
[`LICENSE`]: LICENSE