# [`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... Values>` where
`Values...` are the ("member") types above, in the order they occur in `T`.

An example use case would be along the lines of:

```cpp
template<
    typename    T,
    typename    Value,
    typename... Values
>
void vertex_setup(
    GLTraits::Members<T, Value, Values...>,
    std::size_t offset   = 0,
    GLint       location = 0
)
{
    auto unaligned = offset % alignof(Value);
    if (unaligned)
        offset += alignof(Value) - unaligned;
    GLTraits::Value<Value>::vertex_attrib_pointer(location, offset, sizeof(T));
    vertex_setup(
        GLTraits::Members<T, Values...>{},
        offset   + sizeof(Value),
        location + GLTraits::Value<Value>::columns
    );
}
template<typename T>
void vertex_setup(GLTraits::Members<T>, std::size_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
[`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
