# [`globject`][]

A [C++11][]/[OpenGL][] [\>=1.0][] [object][] library.

The provided `GLObject` class is intended to be the base class of several other
classes that encapsulate different types of OpenGL objects.

[`globject`]: https://git.rcrnstn.net/rcrnstn/globject
[C++11]: https://en.wikipedia.org/wiki/C++11
[OpenGL]: https://en.wikipedia.org/wiki/OpenGL
[\>=1.0]: https://en.wikipedia.org/wiki/OpenGL#Version_history
[object]: https://www.khronos.org/opengl/wiki/OpenGL_Object

## Usage

### OpenGL loading library

This library can be used with an arbitrary [OpenGL loading library][] by making
sure `GLOBJECT_LOADER` is `#define`d to the file to `#include`, e.g.
`<glad/glad.h>` (either in the source or through a compiler switch, probably
defined in the build system). The default if none is defined is `<GL/glew.h>`.

[OpenGL loading library]: https://www.khronos.org/opengl/wiki/OpenGL_Loading_Library

### [OpenGL Mathematics (GLM)][]

If `glm/glm.hpp` is included (specifically, if `GLM_VERSION` is defined) before
inclusion of `globject.hpp` [OpenGL Mathematics (GLM)][] support will be
enabled. See [Data][].

[OpenGL Mathematics (GLM)]: https://glm.g-truc.net
[Data]: #data

### Thread safety

"Global" state (usually used to mirrors some OpenGL [context][] internal state
for convenience or performance) is declared `thread_local`. This means that
multithreading is supported under the assumption that a given OpenGL
[context][] and the `GLObject`s created while it is current are only used from
a single thread.

This is not a huge limitation since driver vendors recommend to not share
OpenGL [context][]s between threads, for performance.

[context]: https://www.khronos.org/opengl/wiki/OpenGL_Context

### Function documentation

For brevity, argument and return types may omit `const &` and functions may
omit `const` qualifiers in the remainder of this documentation when it has no
implication for the user, other than performance. Consult the source for the
exact function signatures.

### `public` interface

#### [Special member functions][]

There is no `public` constructor. It is impossible to directly instantiate
`GLObject`s. Instead, use a derived class that encapsulates a specific type of
OpenGL object.

There is no defined default constructor. The copy constructor and copy/move
assignment operators are `delete`d. This enforces the invariant that a
successfully constructed instance (that has not been moved from) always (is the
only instance that) corresponds to a valid OpenGL object.

The destructor is `virtual`, so references to instances of (different) derived
classes can be held in (the same) containers.

The move constructor's `noexcept` specifier is only honored if `debug` is
`false`, see [Debug][].

[special member functions]: https://en.wikipedia.org/wiki/Special_member_functions
[Debug]: #debug

#### Getters and setters

Getters and setters share the same name and use function overloading to
disambiguate. Getters (which can be run on `const` objects) take no argument
and return the value (by constant reference). Setters take a [forwarding
reference][] argument and return the old value (by value, after move). Getters
and setters use a "gets"/"sets" shorthand below to indicate they conform to
this behaviour.

[forwarding reference]: https://en.cppreference.com/w/cpp/language/reference#Forwarding_references

#### Core

`bool static supported(Version version_min, std::string extension = {})`
returns `true` if the current OpenGL context implements the given, or a later,
version or the given extension. If no version check is desired, provide `{0,
0}`. If no extension check is desired, provide an empty extension string (the
default). If no check at all is performed, `false` is returned. This is
convenient because extensions are often absorbed into newer versions of the
OpenGL specification itself, and some platforms stop reporting support for the
extension for those versions, so only checking if the extension is available
fails. [History of OpenGL][] has a list of which versions of OpenGL absorbed
which extensions. If a non-empty `extension` is provided, it must be on the
form returned by [`glGetStringi`][]`(GL_EXTENSIONS, index)`, i.e. it must start
with `"GL_"`. Since this library is agnostic to the used [OpenGL loading
library][] (which is usually used to perform these kind of checks), the OpenGL
API itself is queried. The old method of detecting the OpenGL version and
extensions was deprecated in OpenGL 3.0 and removed in OpenGL 3.1. Both methods
are dealt with correctly. `Version` is an alias for `std::array<GLint, 2>`.

`GLint static get_integer(GLenum name)` calls [`glGetIntegerv`][] with the
given argument and returns the result.

`GLuint object()` gets the underlying OpenGL object. This is guaranteed to be
valid, unless the `GLObject` has been moved from, in which case it is `0`.

`operator GLuint()` (implicit conversion operator) returns `object()`.

[History of OpenGL]: https://www.khronos.org/opengl/wiki/History_of_OpenGL
[`glGetStringi`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetString.xhtml
[`glGetIntegerv`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGet.xhtml

#### Data

`template<typename Data> struct DataTraits` is template specialized on `GL*`
(and, if enabled, [OpenGL Mathematics (GLM)][]) types and provides the
following `static constexpr` values.

-   `char const[] name`
-   `GLint        columns`
-   `GLint        rows`
-   `GLenum       format`
-   `GLenum       type`
-   `GLenum       internal_format`

It also provides the following functions.

-   `void static attrib(GLuint index, Data value)` which uploads `value` to the
    [non-array attribute][] indicated by `index`. Note that [OpenGL Mathematics
    (GLM)][] matrix types, if enabled, occupy several consecutive indices (one
    per column), which are all uploaded automatically by this call.

-   `void static uniform(GLint location, Data value)` which uploads `value` to
    the [uniform][] indicated by `location` of the current [shader][] program.

`GLOBJECT_DATA*` macros to ease the definition of new template specializations
of `DataTraits` are defined. Consult the source for the definitions and usage
examples.

[non-array attribute]: https://www.khronos.org/opengl/wiki/Vertex_Specification#Non-array_attribute_values
[uniform]: https://www.khronos.org/opengl/wiki/Uniform_(GLSL)
[shader]: https://www.khronos.org/opengl/wiki/Shader

#### Path

`Path` is an alias for `std::string`.

`Paths` is an alias for `std::vector<Path>`.

#### Debug

`bool static debug()` gets/sets the global `debug` flag. When `true`,
potentially costly debug operations are performed as part of other operations.
Defaults to `true`.

`DebugCallback static debug_callback()` gets/sets a callback that may be called
by `GLObject` and its derived classes when `debug` is `true`. The debug
callback can also be called by client code if desired. `DebugCallback` is an
alias for `std::function<void (std::string const & message)>`. Defaults to a
function which outputs to `std::cerr`, appends a newline and flushes.

`DebugObjects static debug_objects()` gets a list of all `GLObjects`
constructed, and not subsequently destructed, during the time `debug` was
`true`. `DebugObjects` is an alias for `std::vector<GLObject *>`.

`std::string debug_name()` returns a short representation of the object.

`std::string debug_info()` returns a potentially longer representation of the
object.

`std::string static debug_objects_name()` returns a short representation of all
objects in `debug_objects`.

`std::string static debug_objects_info()` returns a potentially longer
representation of all objects in `debug_objects`.

Code in `GLObject` (and properly behaved derived classes) uses the macro
`GLOBJECT_DEBUG_IF(D)` which expands to `if (debug() >= 1)` by default. It can
be made to expand to `if (false)` by defining `GLOBJECT_DEBUG 0`, with the
result that an optimizing compiler will completely compile out the
corresponding debug code.

#### Exceptions

All exceptions thrown are of the type `GLObject::Exception`, which inherits
from `std::runtime_error`.

### `protected` interface

#### [Special member functions][]

Derived classes should define a (usually `explicit` non-default) constructor. A
`noexcept` move constructor and `virtual` destructor should only be defined if
resources (other than `object`, which is handled by `GLObject`) need to be
moved/released. Derived classes should not define or `delete` any of the
non-defined/`delete`d special member functions detailed in the `public`
interface documentation above.

Two helper functions that act as adapters from the interface of the
`glCreate*`/`glDelete*` family of functions (used by shader and program
objects, termed "unconventional objects" by the OpenGL Wiki) to the interface
of the `glGen*s`/`glDelete*s` family of functions are provided.

-   `template<GLCreateObject gl_create_object> void static
    gl_create_object_(GLsizei n, GLuint * objects)` takes a `glCreate*`
    function as template argument, and conforms to the `glGen*s` interface.
    `GLCreateObject` is an alias for `GLuint (*)()`.

-   `template<GLDeleteObject gl_delete_object> void static
    gl_delete_object_(GLsizei n, GLuint * objects)` takes a `glDelete*`
    function as template argument, and conforms to the `glDelete*s` interface.
    `GLDeleteObject` is an alias for `void (*)(GLuint)`.

`GLObject` has a concept of pseudo-objects. They are classes that wish to make
use of the `protected` interface of `GLObject` but does not encapsulate an
OpenGL [object][].

The `GLObject` constructor takes:

-   `GLGenObjects gl_gen_objects` which is a function pointer that conforms to
    the `glGen*s` interface. Specify `nullptr` for pseudo-objects.

-   `GLDeleteObjects gl_delete_objects` which is a function pointer that
    conforms to the `glDelete*s` interface. Specify `nullptr` for
    pseudo-objects.

-   `GLenum object_type` which is one of the following. Specify `0` for
    pseudo-objects.

    -   `GL_BUFFER`
    -   `GL_SHADER`
    -   `GL_PROGRAM`
    -   `GL_VERTEX_ARRAY`
    -   `GL_QUERY`
    -   `GL_PROGRAM_PIPELINE`
    -   `GL_TRANSFORM_FEEDBACK`
    -   `GL_SAMPLER`
    -   `GL_TEXTURE`
    -   `GL_RENDERBUFFER`
    -   `GL_FRAMEBUFFER`

-   `std::string object_label` which will be used when throwing exceptions and,
    if OpenGL [\>=4.3][] or the [`KHR_debug`][] extension is available, passed
    to [`glObjectLabel`][].

[\>=4.3]: https://en.wikipedia.org/wiki/OpenGL#Version_history
[`KHR_debug`]: https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_debug.txt
[`glObjectLabel`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glObjectLabel.xhtml

#### Getters and setters

Macros that aid in the implementation of (potentially static thread local)
getters and setters are defined. These should be used in the class definition.
They define functions named `NAME`, which operate on a variable named
`NAME##_`. The underlying variable is not declared automatically, this must be
done manually (probably `private`). The `ACCESS` variant defines both `GET` and
`SET`.

`GLOBJECT_{GET,SET,ACCESS}{,_THREAD}(TYPE, NAME)`

Additionally, static thread local member variables need to be defined in
exactly one translation unit. A macro to help with this is defined as well.
Note that, since the definition is outside the class definition, the `NAME`
needs to be qualified with the class name.

`GLOBJECT_THREAD(NAME, INIT)`

#### Path

`Path static path_prefix_(Path path, Path prefix)` returns `prefix` prepended
to `path`, separated with a `/`, unless

1.  `prefix` is empty
2.  `path` starts with a `/`

in which case `path` is returned as is.

#### TGA

A `TGA` class is provided to handle [Truevision TGA][] images in uncompressed
[BGRA][] (`GL_BGRA`) format. TGA was selected because it is widely supported
and has trivial header and data layout.

`Size` is an alias for `std::array<GLsizei, 2>`.

`Data` is an alias for `std::vector<GLubyte>`.

`explicit TGA(Size size, Data data)` instantiates a `TGA` object with the given
`size` and `data`.

`TGA static read(std::string path)` reads the file `path` into a `TGA` object.

`void write(std::string const & path)` writes the `TGA` object to the file
`path`.

`Data data()` gets the data of the TGA image.

`Size size()` gets the size of the TGA image.

[Truevision TGA]: https://en.wikipedia.org/wiki/Truevision_TGA
[BGRA]: https://en.wikipedia.org/wiki/RGBA_color_model

#### Check

These functions throw an exception if some condition does not hold. It is
recommended that derived classes use similar helper functions, defined in an
implementation file, to hide the string processing necessary to form a helpful
exception message.

Derived classes should treat error checking as free during:

-   Construction and destruction, except move construction.
-   Exceptions.
-   Debug function calls.

At all other times, non-critical error checking should only be performed if
`debug` is `true`.

`void static check_path_(std::string path)` checks that `path` is non-empty.

`void static check_error_(GLenum error)` checks an error returned by
[`glGetError`][].

`void static check_supported_(Version version_min, std::string extension = {})`
checks that `supported` returns `true` for the given arguments.

`void static check_format_(GLenum format, GLenum format_expected)` checks that
`format` matches `format_expected`.

`void static check_type_(GLenum type, GLenum type_expected)` checks that `type`
matches `type_expected`.

`void static check_internal_format_(GLenum internal_format)` checks that
`internal_format` is supported.

[`glGetError`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetError.xhtml

#### Fail

These functions throw an exception and should be marked `[[noreturn]]`. It is
recommended that derived classes use similar helper functions, defined in an
implementation file, to hide the string processing necessary to form a helpful
exception message.

`void fail_action_(std::string action)` throws an exception with a message that
includes `action` and `debug_name()`.

#### String

`std::string static str_path_(Path path)` returns `path` surrounded by quotes.

`std::string static str_paths_(Paths paths)` returns `paths` surrounded by
quotes and joined with commas.

`std::string static str_enum_(GLenum name)` returns the hexadecimal string
representation of `name` (often used as fallback).

`std::string static str_error_(GLenum error)` returns the string representation
of values returned by [`glGetError`][].

`std::string static str_format_(GLenum format)` returns the string
representation of `format`.

`std::string static str_type_(GLenum type)` returns the string representation
of `type`.

`std::string static str_internal_format_(GLenum internal_format)` returns the
string representation of `internal_format`.

## Build system

This project supports [CMake][] and uses [`cmake-common`][]. There are several
ways to use it in other CMake-based projects:

-   With [`find_package`][]: ([Package][] and) [install][] it on the system.

-   With [`add_subdirectory`][]: Bundle it.

-   With [`FetchContent`][]: Download it as part of the CMake configure step.

-   With [`cmake-common`][]: Use any of the above methods through a simplified
    interface.

As usual, use [`add_dependencies`][] or [`target_link_libraries`][] (or
`cmake-common`'s `DEPENDENCIES_*`) to declare the dependency.

[CMake]: https://cmake.org
[`cmake-common`]: https://git.rcrnstn.net/rcrnstn/cmake-common
[`FetchContent`]: https://cmake.org/cmake/help/v3.14/module/FetchContent.html
[`add_subdirectory`]: https://cmake.org/cmake/help/v3.14/command/add_subdirectory.html
[`find_package`]: https://cmake.org/cmake/help/v3.14/command/find_package.html
[Package]: #Package
[Install]: #Install
[`add_dependencies`]: https://cmake.org/cmake/help/v3.14/command/add_dependencies.html
[`target_link_libraries`]: https://cmake.org/cmake/help/v3.14/command/target_link_libraries.html

### Configure and generate

To configure and generate a build tree, use `cmake`:

```sh
cmake -B _build
```

To set the build type, pass e.g. `-D`[`CMAKE_BUILD_TYPE`][]`=Release`.

[`cmake`]: https://cmake.org/cmake/help/v3.14/manual/cmake.1.html#generate-a-project-buildsystem
[`CMAKE_BUILD_TYPE`]: https://cmake.org/cmake/help/v3.14/variable/CMAKE_BUILD_TYPE.html

### Build

To build, use [`cmake --build`][]:

```sh
cmake --build _build
```

To disable building tests, pass `-D`[`BUILD_TESTING`][]`=OFF`.

[`cmake --build`]: https://cmake.org/cmake/help/v3.14/manual/cmake.1.html#build-a-project
[`BUILD_TESTING`]: https://cmake.org/cmake/help/v3.14/module/CTest.html

### Test

To run tests, use [`ctest`][]:

```sh
(cd _build && ctest)
```

To show output from failing tests, pass `--output-on-failure`. To show output
from all tests, pass `--verbose`.

[`ctest`]: https://cmake.org/cmake/help/v3.14/manual/ctest.1.html

### Package

To package, use [`cpack`][]:

```sh
(cd _build && cpack)
```

[`cpack`]: https://cmake.org/cmake/help/v3.14/manual/cpack.1.html

### Install

To install onto the current system, use [`cmake --install`][]:

```sh
cmake --install _build
```

To set the prefix, pass e.g. `-D`[`CMAKE_INSTALL_PREFIX`][]`="$HOME/.local"`.

[`cmake --install`]: https://cmake.org/cmake/help/v3.14/manual/cmake.1.html#install-a-project
[`CMAKE_INSTALL_PREFIX`]: https://cmake.org/cmake/help/v3.14/variable/CMAKE_INSTALL_PREFIX.html

## License

Licensed under the [ISC License][] unless otherwise noted, see the
[`LICENSE`][] file.

[ISC License]: https://choosealicense.com/licenses/isc/
[`LICENSE`]: LICENSE
