# [`glbackend`][]

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

Currently supported backends (one from each category listed under
[Context/Window Toolkits][] on the OpenGL wiki):

| Name          | Category           | Define                | Include                   | Class              |
| --            | --                 | --                    | --                        | --                 |
| [GLFW][]      | Windowing/input    | `GLBACKEND_GLFW`      | `glbackend_glfw.hpp`      | `BackendGLFW`      |
| [SDL2][]      | Multimedia         | `GLBACKEND_SDL`       | `glbackend_sdl.hpp`       | `BackendSDL`       |
| [wxWidgets][] | [Widget toolkit][] | `GLBACKEND_WXWIDGETS` | `glbackend_wxwidgets.hpp` | `BackendWxWidgets` |

[`glbackend`]: https://git.rcrnstn.net/rcrnstn/glbackend
[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
[backend]: https://www.khronos.org/opengl/wiki/Related_toolkits_and_APIs#OpenGL_initialization
[Context/Window Toolkits]: https://www.khronos.org/opengl/wiki/Related_toolkits_and_APIs#Context/Window_Toolkits
[GLFW]: https://www.glfw.org
[SDL2]: https://www.libsdl.org
[wxWidgets]: https://www.wxwidgets.org
[Widget toolkit]: https://en.wikipedia.org/wiki/Widget_toolkit

## Usage

### Overview

```cpp
#include <glbackend_default.hpp>


constexpr auto size = GLBackend::Size{640, 480};


int main()
{
    // Create.
    auto backend = GLBackendDefault("Default", size);

    // Render loop.
    backend.callback_render([&]()
    {
        glClearColor(1.0F, 0.0F, 0.0F, 0.5F);
        glClear(GL_COLOR_BUFFER_BIT);
        if (backend.key("Enter"))
            backend.tga_write("screenshot.tga");
        if (backend.key("Escape"))
            backend.running(false);
    });
    backend.run();
}
```

### Global settings

#### Errors

After backend construction (see [Lifetime](#lifetime)) errors are non-fatal and
are reported to the output configured with

```
using OutputCallback = std::function<void(std::string const & message)>;

static std::ostream * output_stream(std::ostream * output_stream);
static OutputCallback output_callback(OutputCallback output_callback);
```

(defaults to `std::cerr` and empty, respectively). The old values are returned.

If the backend is constructed with `debug = true` (see [Lifetime](#lifetime))
and OpenGL \>=4.3 or the extension [`GL_KHR_debug`][] is available, OpenGL
debug messages with a severity other than `GL_DEBUG_SEVERITY_NOTIFICATION` are
also reported using the same output. The messages received from the OpenGL
implementation are modified slightly to remove trailing whitespace and double
quotes surrounding file names in lines starting with *"path":line*.

[`GL_KHR_debug`]: https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_debug.txt

#### File system

If the value passed to

```
static std::string directory(std::string directory);
```

is non-empty it is prepended, with a separating `"/"`, to all relative paths
(defined as paths not starting with `"/"`) given as arguments to other
functions. The old value is returned.

### Lifetime

Constructors [`throw`][] [`std::runtime_error`][] on failure, with a `what()`
describing the error.

To use a backend, instantiate the appropriate `Backend`-derived class. Several
objects can be instantiated, using the same or different underlying backends.

All the provided `Backend`-derived classes' constructors take the following
arguments, in this order:

-   Window-related:
    -   `std::string const & title`: The window title.
    -   `int width`: The window width.
    -   `int height`: The window height.
    -   `bool fullscreen = false`: Whether or not to create a fullscreen
        window.
    -   `bool transparent = false`: Whether or not to use the alpha channel of
        the default framebuffer to blend with the background. If this is
        enabled, window decorations are disabled and the window is kept on top
        of other windows.
-   [Context][]-related:
    -   `bool debug = false`: If `false`, creates a [no error][] context. If
        `true`, creates a [debug context][], and enables [debug output][].
    -   `int samples = -1`: The number of samples used in
        [mutlisample][multisampling] anti-aliasing (MSAA) of the default
        framebuffer (`-1` lets the implementation decide).
    -   `int version_major = -1`: The major version with which the OpenGL
        [context][] must be compatible (`-1` lets the implementation decide).
    -   `int version_minor = -1`: The minor version with which the OpenGL
        [context][] must be compatible (`-1` lets the implementation decide).

The relevant underlying backend is automatically initialized at instantiation.
If no other code has initialized that backend, it is guaranteed to be
terminated when the last instance is destroyed. It is backend-specific if the
backend is terminated even if other code has initialized it.

The provided `Backend`s create double buffered, 32 bit color (including alpha),
24 bit depth, 8 bit stencil OpenGL [context][]s. If the requested version is
\>=3.2 a [forward compatible][] core context is created.

The `Backend`s are move(-constructible)-only types.

[`throw`]: https://en.cppreference.com/w/cpp/language/throw
[`std::runtime_error`]: https://en.cppreference.com/w/cpp/error/runtime_error
[context]: https://www.khronos.org/opengl/wiki/OpenGL_Context
[debug context]: https://www.khronos.org/opengl/wiki/Debug_Context
[debug output]: https://www.khronos.org/opengl/wiki/Debug_Output
[no error]: https://www.khronos.org/opengl/wiki/OpenGL_Context#No_error_contexts
[multisampling]: https://www.khronos.org/opengl/wiki/Multisampling
[forward compatible]: https://www.khronos.org/opengl/wiki/OpenGL_Context#Forward_compatibility

### Info

```
std::string info() const;
```

returns information about the underlying backend.

### Context

Each `Backend` instance has its own OpenGL [context][], which is made current
at instantiation. If several `Backend`s are used,

```
void current();
```

can be called to make the context of a specific one current.

### Render loop

```
void events();
```

handles events from the underlying backend.

```
void swap();
```

swaps the OpenGL default framebuffer.

```
bool running() const;
bool running(bool running);
```

queries and sets, respectively, the window "running" state.

```
float time() const;
float time(float time);
```

queries and sets, respectively, the current time, in seconds.

```
using Update = std::function<void(float t, float dt, bool final)>;
using Render = std::function<void()>;

Backend & update(Update const & update);
Backend & render(Render const & render);
```

sets the `update` and `render` callbacks, respectively, used by

```
void run(float dt_max = 0.0F);
```

which is a convenience function that uses the member functions and callbacks
described in this section to implement a render loop. `t` is the current time,
which is set to `0.0F` at the start of `run`. If `dt_max` is `0.0F`, the
`update` callback is called once for every frame, with `dt` holding the elapsed
time. Otherwise, the `update` callback is called as many times as necessary to
advance through the elapsed time while ensuring `dt` is never larger than
`dt_max`. `final` is `true` if this is the final update of the frame. `render`
is called at the end of each frame. Either of the callbacks can be unset.

### Input and output

```
void lock(bool lock);
```

controls mouse locking. If `true` is passed the mouse is locked, which means it
is hidden and prevented from leaving the window. Passing `false` restores the
normal behavior.

The input (keyboard and mouse) and output (framebuffer) state can be accessed
in two different ways: registering callbacks and / or polling. The same
function name is used for both methods (with function overloading). These are
described below.

#### Keyboard

The

```
key
```

*callback / poll* takes as argument a `std::string const & key` and *is called
when / returns `true` if* that key is pressed. Valid arguments are
backend-specific but guaranteed to support:

-   `"A"` through `"Z"`
-   `"0"` through `"9"`
-   `"Left"`, `"Right"`, `"Up"`, `"Down"`
-   `"Enter"`, `"Escape"`, `"Tab"`, `"Backspace"`
-   `"Control"`, `"Shift"`, `"Alt"`

#### Mouse

The

```
button
```

*callback / poll* takes as argument an `int button` and *is called when /
returns `true` if* that button is pressed. Valid arguments are backend-specific
but guaranteed to support `1`, `2`, and `3`.

The

```
scroll
```

*callback / poll* takes as *argument / returns* `std::array<float, 2> scroll`.

The

```
position
```

*callback / poll* takes as *argument / returns* `std::array<float, 2>
position`.

The

```
move
```

*callback / poll* takes as *argument / returns* `std::array<float, 2> move`.

#### Framebuffer

The

```
size
```

*callback / poll* takes as *argument / returns* `std::array<int, 2> size`.

### Persisting frames

There is basic support for writing frames to disk, useful for testing or
screenshot functionality.

Writing is done with

```
bool frame_tga_write(
    std::string const & path
) const;
```

As the name suggests, the frame is written as an uncompressed [BGRA][]
[Truevision TGA][] image, selected because it is widely supported and has a
trivial header and data layout. The function returns `true` on success, on
failure an [error](#errors) is emitted and `false` is returned.

```
bool frame_tga_compare(
    std::string const & path,
    bool write_on_failed_read = false
) const;
```

can be used to compare the current frame to one written previously. Note that a
general TGA reader is not used; the comparison is done byte for byte, header
included. If the given `path` cannot be read an [error](#errors) is emitted and
if `write_on_failed_read` is `true` the current frame is instead written to
`path`.

`".tga"` is automatically added to the end of `path`. In addition, `directory`
is applied if appropriate, see [File system](#file-system).

No function is provided to read the frame without interacting with the file
system. If this is required, simply use [`glReadPixels`][].

[BGRA]: https://en.wikipedia.org/wiki/RGBA_color_model
[Truevision TGA]: https://en.wikipedia.org/wiki/Truevision_TGA
[`glReadPixels`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glReadPixels.xhtml

## Dependencies

System dependencies that need to be installed:

-   Public (interface):
    -   [OpenGL][] (e.g. [`libgl1-mesa-dev`][]).
    -   [OpenGL Extension Wrangler (GLEW)][] (e.g. [`libglew-dev`][]).
-   Private (tests):
    -   [GLFW][] (e.g. [`libglfw3-dev`][]).
    -   [SDL2][] (e.g. [`libsdl2-dev`][]).
    -   [wxWidgets][] (e.g. [`libwxgtk3.0-gtk3-dev`]).

[OpenGL Extension Wrangler (GLEW)]: http://glew.sourceforge.net
[`libgl1-mesa-dev`]: https://packages.debian.org/search?keywords=libgl1-mesa-dev
[`libglew-dev`]: https://packages.debian.org/search?keywords=libglew-dev
[`libglfw3-dev`]: https://packages.debian.org/search?keywords=libglfw3-dev
[`libsdl2-dev`]: https://packages.debian.org/search?keywords=libsdl2-dev
[`libwxgtk3.0-gtk3-dev`]: https://packages.debian.org/search?keywords=libwxgtk3.0-gtk3-dev

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