# [`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 <array> #include <GL/glew.h> #include <glm/glm.hpp> #include <glshader.hpp> #include <glshader_glm.hpp> // Global data. constexpr auto light_count_max = 32; constexpr auto vert_position = 0; constexpr auto vert_tex_coord = 1; constexpr auto frag_color = 0; int main() { // Local data. auto light_count = 3; auto color = glm::vec4{1, 0, 0, 1}; struct { std::array<float, 4> value1; std::array<float, 4> value2; } values = {}; auto texture0 = GLuint{}; glGenTextures(1, &texture0); // Global settings. Shader::root("assets/shaders"); Shader::defines({ {"light_count_max", std::to_string(light_count_max)}, }); Shader::verts({ {"vert_position", vert_position}, {"vert_tex_coord", vert_tex_coord}, }); Shader::frags({ {"frag_color", frag_color}, }); // Create. auto player = Shader({ "player.vert", "player.frag", "lighting.frag", }); // Use. player .use() .uniform("light_count", light_count) .uniform("color", color) .uniform("values", values) .texture("texture0", texture0, GL_TEXTURE_2D) .validate(); Shader::uniform_buffer("values", values); } ``` 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). - Uniforms not set with a call to [`uniform`](#uniforms), uniform blocks not set with a call to `uniform` or `uniform_buffer`. (OpenGL defaults them to zero unless initialized in the shader.) 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, such as calling [`uniform`](#uniforms). 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 ### User-defined vertex inputs (attributes) and fragment outputs (data) Locations for user-defined [vertex inputs][] (attributes) and [fragment outputs][] (data, requires OpenGL >= 3.0) can be specified by calling `Shader::verts(Shader::Locations const & verts)` and `Shader::frags(Shader::Locations const & frags)` respectively before instantiating a `Shader` that uses them. `Shader::Locations` is an alias for `std::map<std::string, GLuint>`. Any `location` [layout qualifier][] overrides the values specified this way. Note that a `Shader` object retains the input and output locations that were in effect when it was instantiated. See [`glBindAttribLocation`][] and [`glBindFragDataLocation`][]. Note that fragment outputs are subject to [`glDrawBuffer`][] / [`glDrawBuffers`][]. An error is thrown if a shader uses a non-specified input. No error is thrown for non-specified outputs (this is mostly a result of the inability to enumerate shader outputs before the introduction of the [program interface query][]). Shaders need not use all of the specified inputs/outputs. The intent is for the application to define global unchanging input/output locations that are used by all shaders, geometry and framebuffers and set them up once at application startup. [vertex inputs]: https://www.khronos.org/opengl/wiki/Vertex_Shader#Inputs [fragment outputs]: https://www.khronos.org/opengl/wiki/Fragment_Shader#Outputs [layout qualifier]: https://www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL) [`glBindAttribLocation`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBindAttribLocation.xhtml [`glBindFragDataLocation`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBindFragDataLocation.xhtml [`glDrawBuffer`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDrawBuffer.xhtml [`glDrawBuffers`]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDrawBuffers.xhtml [program interface query]: https://www.khronos.org/opengl/wiki/Program_Introspection#Interface_query ### Uniforms [Uniform][]s are set with the template specialized `uniform(std::string const & name, T const & value, bool required = true)`. If `required` is `true` an error is thrown if the shader does not contain an active uniform with the given `name`. If the macro [`NDEBUG`][] is not defined an error is thrown if the value is of the wrong type. [`use`](#use) must be called before calling `uniform`. [uniform]: https://www.khronos.org/opengl/wiki/Uniform #### Scalar uniforms Template specializations are provided for all `GL*` types corresponding to a `glUniform1*` variant. As special cases, `GLboolean` and `bool` map to the `i` (`GLint`) variant. The `GLdouble` specialization is [`delete`][]d to avoid accidental conversions, change the type (with an `f`/`F` suffix in case of literals) or use an explicit cast to get the `GLfloat` specialization. [`delete`]: https://en.cppreference.com/w/cpp/language/function#Deleted_functions #### OpenGL Mathematics (GLM) [OpenGL Mathematics (GLM)][] specializations are provided in the include file `glshader_glm.hpp`. OpenGL Mathematics (GLM) uses [OpenGL Shading Language (GLSL)][] naming conventions. [OpenGL Mathematics (GLM)]: https://glm.g-truc.net [OpenGL Shading Language (GLSL)]: https://www.khronos.org/opengl/wiki/OpenGL_Shading_Language #### User-defined uniforms Support for e.g. third party mathematics libraries can be added by providing the relevant template specializations of `uniform`. The macro `GLSHADER_UNIFORM(TYPE, CODE)` may be used to ease this task. `TYPE` is the type for which to provide the specialization and `CODE` is code that may use the supplied variables `TYPE const & value` and `GLint const & location`, probably as parameters to `glUniform*`. `GLSHADER_UNIFORM_DELETE(TYPE)` can be used to [`delete`][] the specialization for a given type. #### Uniform blocks / buffers If no specialization is provided, [uniform block][]s are assumed. This requires OpenGL \>=3.1 or the [`ARB_uniform_buffer_object`][] extension, an error is thrown if it is not available. Shared, by name, [Uniform Buffer Object (UBO)][]s are automatically allocated. Beware of alignment, it is recommended to use the `std140` [memory layout][] and to [avoid `vec3`/`mat3`][]. Bare arrays are not supported, wrap them in uniform blocks. [Uniform block]: https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Uniform_blocks [`ARB_uniform_buffer_object`]: https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_uniform_buffer_object.txt [Uniform Buffer Object (UBO)]: https://www.khronos.org/opengl/wiki/Uniform_Buffer_Object [memory layout]: https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Memory_layout [avoid `vec3`/`mat3`]: https://stackoverflow.com/questions/38172696/should-i-ever-use-a-vec3-inside-of-a-uniform-buffer-or-shader-storage-buffer-object #### Textures As a special case, uniform texture samplers can be set by calling `texture(std::string const & name, GLuint texture, GLenum target, bool required = true)`. Unless a texture unit is already correctly set up as a result of a previous call, one is automatically allocated and its `target` bound to `texture`. If a texture unit needs to be allocated but no unused one is available (the limit can be queried with [`glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, ...)`][glGet] the least recently used one is reused. [`uniform`](#uniforms) is then called with the given `name` (and `required`) and the allocated texture unit as the `value`. Subsequent calls with a given `texture` are very cheap provided that the number of other textures set since the previous call stay below the limit. If a subsequent call for a given `texture` uses a different `target` an error is thrown. [glGet]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGet.xhtml ## Dependencies Public (interface): - [OpenGL][], system (e.g. [`libgl1-mesa-dev`][]). - [OpenGL Extension Wrangler (GLEW)][], system (e.g. [`libglew-dev`][]). - [OpenGL Mathematics (GLM)][], **optional**, system (e.g. [`libglm-dev`][]). Private (build): - [`str`][], downloaded as part of the CMake configure step. Private (tests): - [GLFW][], system (e.g. [`libglfw3-dev`][]). - [OpenGL Mathematics (GLM)][], system (e.g. [`libglm-dev`][]). - [`gltest`][], downloaded as part of the CMake configure step. [OpenGL Extension Wrangler (GLEW)]: http://glew.sourceforge.net [OpenGL Mathematics (GLM)]: https://glm.g-truc.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 [`libglm-dev`]: https://packages.debian.org/search?keywords=libglm-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