# [`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
#### `#include`s
Support for [`#include`][]s is provided, with the same syntax as the
[`ARB_shading_language_include`][] extension. An `#include`d path must be
surrounded by `<` `>`, in which case it is treated as relative to
[`root`](#loading-files), or by `"` `"`, in which case it is treated as
relative to the including file.
Even though the extension is not used for including files, an `#extension
GL_ARB_shading_language_include : require` line is required for portability,
otherwise an error is thrown. The line is silently dropped before the source is
fed to OpenGL so as to not upset the shader compiler if the extension is not
available.
If the [`ARB_shading_language_include`][] extension is available, `#line`
statements that include the file path are injected before every line (after the
`#version`) to improve error messages from the shader compiler.
Note that the `#include` statements are expanded before the source is fed to
OpenGL, and therefore before the [GLSL preprocessor][] is run which means
things like [include guard][]s do not work. (For portability, it is recommended
that include guards be used anyway.) Circular includes are broken by skipping
`#include`s where the *including file, line number, included file* tuple has
been seen before.
[`#include`]: https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)#Preprocessor_directives
[`glCompileShader`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glCompileShader.xhtml
[GLSL preprocessor]: https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)#Preprocessor_directives
[include guard]: https://en.wikipedia.org/wiki/Include_guard
[`ARB_shading_language_include`]: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shading_language_include.txt
[`glNamedStringARB`]: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shading_language_include.txt
[`glCompileShaderIncludeARB`]: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shading_language_include.txt
## 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