# [`glshader`][]
A [C++11][] [OpenGL][] \>=[2.0][] [shader][] library.
[`glshader`]: https://git.rcrnstn.net/rcrnstn/glshader
[C++11]: https://en.wikipedia.org/wiki/C++11
[OpenGL]: https://en.wikipedia.org/wiki/OpenGL
[2.0]: https://en.wikipedia.org/wiki/OpenGL#Version_history
[shader]: https://www.khronos.org/opengl/wiki/Shader
## Usage
Overview of usage:
```cpp
#include <GL/glew.h>
#include <glshader.hpp>
// Global data.
constexpr auto light_count_max = 32;
int main()
{
// Global settings.
Shader::root("assets/shaders");
Shader::defines({
{"light_count_max", std::to_string(light_count_max)},
});
// Create.
auto player = Shader({
"player.vert",
"player.frag", "lighting.frag",
});
// Use.
player
.use()
.validate();
}
```
All public (non-static) member functions return a reference to the current
object, allowing [method chaining][].
[method chaining]: https://en.wikipedia.org/wiki/Method_chaining
### Errors
Errors are handled by throwing [`std::runtime_error`][] with a [`what()`][]
that returns helpful context, including operating system messages and the
OpenGL info log where appropriate.
If OpenGL \>=4.3 or the extension [`GL_KHR_debug`][] is available,
[`glObjectLabel`][] is used to improve debug messages provided to any callback
registered with [`glDebugMessageCallback`][].
Se [`tests/glshader.cpp`][] for an overview of some of the handled errors.
(Note that the tests are written for compatibility and convenience and do not
necessarily reflect current best practices.)
[`std::runtime_error`]: https://en.cppreference.com/w/cpp/error/runtime_error
[`what()`]: https://en.cppreference.com/w/cpp/error/exception/what
[`GL_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
[`glDebugMessageCallback`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDebugMessageCallback.xhtml
[`tests/glshader.cpp`]: tests/glshader.cpp
### Lifetime
No default constructor is defined. This, together with a throwing constructor,
means that a (non-moved-from) `Shader` object always corresponds to a valid
OpenGL shader object.
At destruction, the underlying OpenGL shader program is deleted, but not
explicitly unbound. Note that the behavior of [deletion unbinding][] depends on
the OpenGL version. It is recommended to always consider the underlying OpenGL
shader program to be invalid after destruction.
Copying (creating a new identical underlying OpenGL shader object) or move
assigning (deleting the underlying OpenGL shader object of the moved-into
`Shader` object) would probably be a (performance) bug and is disabled. I.e.
`Shader` is a move(-constructible)-only type.
[deletion unbinding]: https://www.khronos.org/opengl/wiki/OpenGL_Object#Deletion_unbinding
### Loading files
An arbitrary number of files can be loaded by giving them as arguments to the
`Shader(Shader::Paths const & paths)` constructor.
`Shader::Paths` is an alias for `std::set<std::string>`. `std::set` is used
over e.g. `std::vector` to facilitate deduplicating shaders based on paths,
even when the paths are given in different orders. `std::set` is used over
`std::unordered_set` to retain the same ordering in error messages as in the
creation for easy identification.
The [shader stage][] is automatically detected by the file extension using the
same rules as the [OpenGL / OpenGL ES Reference Compiler][]:
- `.vert`: vertex
- `.tesc`: tessellation control
- `.tese`: tessellation evaluation
- `.geom`: geometry
- `.frag`: fragment
- `.comp`: compute
If multiple files are provided for the same stage they are compiled separately
and linked into the final shader program.
The value passed to `Shader::root(std::string const & root)` is prepended, with
a separating `"/"` if non-empty, to all paths (defaults to `""`).
[shader stage]: https://www.khronos.org/opengl/wiki/Shader#Stages
[OpenGL / OpenGL ES Reference Compiler]: https://www.khronos.org/opengles/sdk/tools/Reference-Compiler/
### Validation
By calling `validate()` the shader can be [validate][]d against the current
OpenGL state to make sure that a subsequent draw call would succeed. An error
is thrown if a problem is detected.
Additional issues that are not typically detected by [`glValidateProgram`][]
are checked for:
- Shader program not [current](#use).
If the macro [`NDEBUG`][] is defined, the check is optimised out. Therefore, it
is recommended to always call `validate()` before issuing draw calls.
[validate]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glValidateProgram.xhtml
[`glValidateProgram`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glValidateProgram.xhtml
[`NDEBUG`]: https://en.cppreference.com/w/c/error/assert
### Use
The shader needs to be [use][]d by calling `use()` before performing certain
other actions on it.
If the macro [`NDEBUG`][] is not defined, an error is thrown if this is not the
case.
[use]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glUseProgram.xhtml
### Preprocessing
A simple preprocessor is run before shader sources are passed to OpenGL.
At present the preprocessor does not interpret line continuations or multi-line
comments correctly on lines it acts upon. Other lines are left alone so they
can be used there without issue.
#### `#version`
Each supplied shader file is checked for the existence of one, and only one,
[`#version`][]. An error is thrown if this is not the case. (The OpenGL default
is to use `#version 110` if none is provided.)
[`#version`]: https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)#Version
#### `#define`s
[`#define`][]s specified with `Shader::defines(Shader::Defines const &
defines)` are injected after the `#version`. `Shader::Defines` is an alias for
`std::map<std::string, std::string>`.
[`#define`]: https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)#Preprocessor_directives
## Dependencies
Public (interface):
- [OpenGL][], system (e.g. [`libgl1-mesa-dev`][]).
- [OpenGL Extension Wrangler (GLEW)][], system (e.g. [`libglew-dev`][]).
Private (build):
- [`str`][], downloaded as part of the CMake configure step.
Private (tests):
- [GLFW][], system (e.g. [`libglfw3-dev`][]).
- [`gltest`][], downloaded as part of the CMake configure step.
[OpenGL Extension Wrangler (GLEW)]: http://glew.sourceforge.net
[GLFW]: https://www.glfw.org
[`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
[`str`]: https://git.rcrnstn.net/rcrnstn/str
[`gltest`]: https://git.rcrnstn.net/rcrnstn/gltest
## 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