/* This file is part of Magnum. Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Vladimír Vondruš Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ namespace Magnum { /** @page shaders Builtin shaders @brief Overview and basic usage of builtin shaders. @m_keywords{Shaders} @tableofcontents @m_footernavigation Magnum contains a set of general-purpose shaders for easy prototyping, UI rendering and data visualization/debugging in both 2D and 3D scenes. The following shaders are available: - @ref Shaders::FlatGL "Shaders::FlatGL*D" --- flat shading using single color or texture - @ref Shaders::VectorGL "Shaders::VectorGL*D" --- colored vector graphics - @ref Shaders::DistanceFieldVectorGL "Shaders::DistanceFieldVectorGL*D" -- colored and outlined vector graphics - @ref Shaders::VertexColorGL "Shaders::VertexColorGL*D" --- vertex-colored meshes - @ref Shaders::PhongGL --- Phong shading using colors or textures, 3D only - @ref Shaders::MeshVisualizerGL2D / @ref Shaders::MeshVisualizerGL3D --- wireframe visualization The essential functionality of builtin shaders can be used even on unextended OpenGL 2.1 and OpenGL ES 2.0 / WebGL 1.0, but the code will try to use the most recent technology available to have them as efficient as possible on every configuration. Some functionality, such as uniform buffers, texture arrays or object ID rendering, requires newer versions or extensions, as noted in documentation of a particular feature. @section shaders-usage Usage Shader usage is divided into two parts: describing vertex attributes in the mesh and setting up the shader itself. Each shader expects some set of vertex attributes, thus when adding a vertex buffer into the mesh, you need to specify which shader attributes are on which position in the buffer. See @ref GL::Mesh::addVertexBuffer() for details and usage examples. Example mesh configuration for the @ref Shaders::PhongGL shader: @snippet MagnumShaders-gl.cpp shaders-setup Each shader then has its own set of configuration functions. Some configuration is static, specified commonly as flags in constructor, directly affecting compiled shader code. Other configuration is specified through uniforms and various binding points, commonly exposed through various setters. For uniforms there's two different workflows --- a classical one, where uniforms have immediate setters, and a uniform buffer workflow, where the uniform parameters are saved to a structure and then uploaded to a GPU buffer. Let's compare both approaches: @subsection shaders-usage-classic Using classic uniforms The most straightforward and portable way, working even on old OpenGL ES 2.0 and WebGL 1.0 platforms, is using classic uniform setters. All shader uniforms have a reasonable defaults so you are able to see at least something when using the shader directly without any further configuration, but in most cases you may want to specify at least the transformation/projection matrices. Example configuration and rendering using @link Shaders::PhongGL @endlink --- by default it's just colored and uses a single light, and we set a color of both in addition to transformation, projection and normal matrices: @snippet MagnumShaders-gl.cpp shaders-classic @subsection shaders-usage-ubo Using uniform buffers Uniform buffers require GL 3.1, OpenGL ES 3.0 or WebGL 2.0 and are more verbose to set up, but when used the right way they can result in greatly reduced driver overhead. Uniform buffers get enabled using the @relativeref{Shaders::PhongGL,Flag::UniformBuffers} flag that's implemented for all builtin shaders, and after that you're not supposed to use most of the `set*()` APIs anymore, instead you have to fill uniform structures, upload them to @ref GL::Buffer instances and then bind those via various `bind*Buffer()` APIs. To simplify porting, documentation of each classic uniform setter lists the equivalent uniform buffer APIs. Because some parameters such as projection, material or light setup don't change every draw, they are organized into buffers based on expected frequency of change. This way you can fill the projection and material buffers just once at the start, light setup only when the camera position changes and with much less to upload for every draw. The separation is also done in a way that makes it possible to reuse projection/transformation data among different shaders, e.g. for a depth pre-pass. In the following example, projection and transformation parameters are supplied via generic shader-independent @ref Shaders::ProjectionUniform3D and @ref Shaders::TransformationUniform3D structures and Phong-specific parameters then via @ref Shaders::PhongDrawUniform, @ref Shaders::PhongMaterialUniform and @ref Shaders::PhongLightUniform structures. While the structures expose the fields directly, the data layout may be non-trivial and it's thus recommended to use the setters unless they prove to be a performance bottleneck: @snippet MagnumShaders-gl.cpp shaders-ubo Altogether, this results in the same output as in the classic uniform case shown above. Similarly to the classic uniforms, default-constructed structures have reasonable defaults to make the shader render at least something, but note that you *have to* bind the buffer to get the defaults, without a buffer bound you'll get a fully black mesh at best and nothing rendered at all in the worst cases. @m_class{m-block m-success} @par Importance of buffer usage and storage flags With uniform buffers, it's very important what kind of memory gets used for the backing storage. In the above snippets, the (implicit) @ref GL::BufferUsage::StaticDraw got used for simplicity, but for data that are changed for every draw it could make sense to pick @ref GL::BufferUsage::DynamicDraw instead. @par The most ideal way may be to use @gl_extension{ARB,buffer_storage} from OpenGL 4.4, and instead of @ref GL::Buffer::setData() calling @relativeref{GL::Buffer,setStorage()} directly with the uniform data and with empty @ref GL::Buffer::StorageFlags, which makes the buffer immutable. Updating such immutable buffer can be still done via @ref GL::Buffer::copy() from another buffer, but setting or mapping the data from the CPU side won't be possible. @par As with everything, be sure to profile and pick the best workflow for your target platform --- what's best for desktop may not be the best in WebGL, and what works with WebGL running on top GL may not be the best with WebGL that's itself implemented using D3D or Metal. @subsection shaders-usage-multidraw Multidraw and reducing driver overhead The main advantage of uniform buffers is the ability to specify data for multiple draws together --- after all, having to reupload three or four buffers for every draw like shown above wouldn't be really faster or easier than setting the uniforms directly. On the other hand, uploading everything first and binding a different subrange each time would avoid the reupload, but since most drivers have uniform buffer alignment requirement as high as 256 bytes (@ref GL::Buffer::uniformOffsetAlignment()), the per-draw buffers would have to be very sparse. Instead, it's possible to construct the shaders with a statically defined draw count, fill the buffers with data for that many draws at once and then use @relativeref{Shaders::PhongGL,setDrawOffset()} to pick concrete per-draw parameters. Since material parameters are commonly shared among multiple draws, the desired usage is to upload unique materials and then reference them via a @ref Shaders::PhongDrawUniform::materialId "Shaders::*DrawUniform::materialId". The following snippet shows drawing three different meshes, where two of them share the same material definition. The projection and light buffer is the same as above: @snippet MagnumShaders-gl.cpp shaders-multi While this minimizes the state changes to just a single immediate uniform being changed between draws, it's possible to go even further by using @ref GL::MeshView instances onto a single @ref GL::Mesh instead of several different @ref GL::Mesh objects --- that way the attribute layout doesn't need to be updated and it's just submitting draws with different offsets and counts. Finally, with mesh views and on platforms that support @gl_extension{ARB,shader_draw_parameters} from OpenGL 4.6 or the @gl_extension{ANGLE,multi_draw} / @webgl_extension{WEBGL,multi_draw} ES and WebGL extension, it's possible to directly submit a multi-draw command. The shader needs to have @relativeref{Shaders::PhongGL,Flag::MultiDraw} enabled, which will make it use the @glsl gl_DrawID @ce builtin to pick the per-draw parameters on its own. The above snippet modified for multidraw would then look like this, uniform upload and binding is the same as before: @snippet MagnumShaders-gl.cpp shaders-multidraw @m_class{m-block m-warning} @par Uniform buffer size limits Note that size of a single uniform buffer that can be bound to a shader is quite limited (@ref GL::AbstractShaderProgram::maxUniformBlockSize(), usually just 16 or 64 kB), and another reason the parameters are separated and deduplicated among several buffers is to maximize use of that memory. With that you should always be able to submit at least 256 draws at once as the biggest per-draw uniform structure used by builtin shaders has a size of a 4x4 matrix. See documentation of @ref Shaders::FlatGL::Configuration::setDrawCount() "Shaders::*::Configuration::setDrawCount()" and related APIs of a particular shader for concrete limits. @par For larger batches the expected workflow is to still upload everything at once but then bind and draw smaller (and properly aligned) subranges that fit into the limit. For convenience, all uniform structures are guaranteed to fit evenly into multiples of 768 bytes, which should be large enough for even the strictest @ref GL::Buffer::uniformOffsetAlignment() requirements. @par An alternative solution to overcome the size limits is to enable @relativeref{Shaders::PhongGL,Flag::ShaderStorageBuffers} instead (available on OpenGL 4.3+ and OpenGL ES 3.1+), which have no size limits. The cost is narrower platform support and potentially slower access compared to uniform buffers. @subsection shaders-usage-instancing Instancing @ref Shaders::FlatGL and @ref Shaders::PhongGL support instancing, which allows them to render the same mesh several times but with different transformation and material applied. It can be thought of as a more constrained variant of the multidraw mentioned above, but instead of uniform buffers the per-instance parameters are passed through instanced mesh attributes. No uniform buffer requirement means this feature can be used even on OpenGL ES 2.0 and WebGL 1.0 targets if corresponding instancing extensions are available. Using attributes instead of uniform buffers also means there's no limitation on how many instances can be drawn at once, on the other hand a mesh can have only a certain amount of attribute bindings and thus only the basic properties can be specified per-instance such as the transformation matrix or base color. The following snippet shows a setup similar to the multidraw above, except that it's just the same sphere drawn three times in different locations and with a different material applied. Note that the per-instance color is achieved by using the usual vertex color attribute, only instanced: @snippet MagnumShaders-gl.cpp shaders-instancing @subsection shaders-usage-skinning Skinning @ref Shaders::FlatGL, @ref Shaders::MeshVisualizerGL3D "Shaders::MeshVisualizerGL*D" and @ref Shaders::PhongGL are capable of rendering skinned meshes. Such meshes are commonly imported from files such as glTF or FBX together with the skeleton hierarchy and associated animations. The following snippet shows compiling a @ref Trade::MeshData to a @ref GL::Mesh using @ref MeshTools::compile(), using @ref Trade::SkinData and @ref MeshTools::compiledPerVertexJointCount() to set up shader parameters and finally uploading calculated joint matrices to perform the skinning animation: @snippet MagnumShaders-gl.cpp shaders-skinning The above hardcodes the joint counts in the shader, which makes it the most optimal for rendering given mesh. However, with multiple skinned meshes it'd mean having a dedicated shader instance tailored for each. To avoid that, you can set the joint count and per-vertex joint count to the maximum that the meshes would need, enable @ref Shaders::PhongGL::Flag::DynamicPerVertexJointCount "Flag::DynamicPerVertexJointCount", for a particular draw upload just a subset of joint matrices the mesh would reference and set the count of actually used per-vertex joints via @ref Shaders::PhongGL::setPerVertexJointCount(). Thus, compared to above: @snippet MagnumShaders-gl.cpp shaders-skinning-dynamic @subsection shaders-usage-textures Using textures Unless the shader requires a texture to work (which is the case of @ref Shaders::VectorGL and @ref Shaders::DistanceFieldVectorGL), by default all shaders are just colored. Enabling a texture is done via a flag (such as @ref Shaders::PhongGL::Flag::DiffuseTexture) and then the texture is bound via an appropriate `bind*Texture()` call. In most cases the texture value is multiplied with the corresponding color uniform. @snippet MagnumShaders-gl.cpp shaders-textures All shaders that support textures are also able to apply arbitrary transformation to the texture coordinate attribute by enabling @relativeref{Shaders::PhongGL,Flag::TextureTransformation} on a particular shader. Desired transformation is then supplied via @relativeref{Shaders::PhongGL,setTextureMatrix()} (or a @ref Shaders::TextureTransformationUniform in case uniform buffers are used). This can be useful for animations, when you have a larger atlas with switchable texture variations for a single mesh, or when you have texture coordinates quantized in some nontrivial way. Texture transformation is also useful in the @ref shaders-usage-multidraw "multidraw" or @ref shaders-usage-instancing "instancing" scenarios mentioned above, since each draw will most likely require a different texture. There are two options: - Upload the textures to subrectangles of a larger @ref GL::Texture2D and then specify @ref Shaders::TextureTransformationUniform::offset and @relativeref{Shaders::TextureTransformationUniform,rotationScaling} for each draw, or in case of an instanced draw supply an instanced @relativeref{Shaders::PhongGL,TextureOffset} attribute and have a global scale set for all instanced via @relativeref{Shaders::PhongGL,setTextureMatrix()}. - Enable @relativeref{Shaders::PhongGL,Flag::TextureArrays} in the shader (not available on OpenGL ES 2.0 or WebGL 1.0), upload the textures to slices of a @ref GL::Texture2DArray and specify @ref Shaders::TextureTransformationUniform::layer for each draw, or in case of an instanced draw supply a layer in an instanced @relativeref{Shaders::PhongGL,TextureOffsetLayer} attribute. While with a @ref GL::Texture2D you may hit texture size limits (not to mention you possible issues with materials that relied on a certain wrapping mode), @ref GL::Texture2DArray is generally able to contain a lot more data, however all slices have to be of the same size. You can also combine the two approaches and pack differently sized textures to slices of a texture array and then set both offset/scale and a layer per-draw. The following snippet shows a multi-draw setup with a different texture array layer used by each draw. While the projection, transformation, draw material and light buffers are the same as before, there's a new per-draw @ref Shaders::TextureTransformationUniform buffer supplying the layer information: @snippet MagnumShaders-gl.cpp shaders-texture-arrays While the primary use case of texture arrays is with uniform buffers and multidraw, they work in the classic uniform workflow as well --- use @relativeref{Shaders::PhongGL,setTextureLayer()} there instead. @section shaders-async Async shader compilation and linking By default, shaders are compiled and linked directly in their constructor. While that's convenient and easy to use, applications using heavier shaders, many shader combinations or running on platforms that translate GLSL to other APIs such as HLSL or MSL, may spend a significant portion of their startup time just on shader compilation and linking. To mitigate this problem, shaders can be compiled in an asynchronous way. Depending on the driver and system, this can mean that for example eight shaders get compiled at the same time in eight parallel threads, instead of sequentially one after another. To achieve such parallelism, the construction needs to be broken into two parts --- first submitting compilation of all shaders using @ref Shaders::FlatGL::compile() "Shaders::*GL::compile()", forming temporary @ref Shaders::FlatGL::CompileState "Shaders::*GL::CompileState" instances, then possibly doing other work until it's completed, and finally constructing final shader instances out of the temporary state: @snippet MagnumShaders-gl.cpp shaders-async The above code will work correctly also on drivers that implement async compilation partially or not at all --- there @ref GL::AbstractShaderProgram::isLinkFinished() will implicitly return @cpp true @ce, and the final construction will stall if it happens before a (potentially async) compilation is finished. See also the @ref GL-AbstractShaderProgram-async "GL::AbstractShaderProgram documentation" for more information. @section shaders-generic Generic vertex attributes and framebuffer attachments Many shaders share the same vertex attribute definitions, such as positions, normals, texture coordinates etc. It's thus possible to configure the mesh for a *generic* shader and then render it with any compatible shader. Definition of all generic attributes is available in the @ref Shaders::GenericGL class. Setup of the mesh @ref shaders-usage "shown above" using generic attributes could then look like this: @snippet MagnumShaders-gl.cpp shaders-generic Note that in this particular case both setups are equivalent, because @ref Shaders::PhongGL attribute definitions are just aliases to the generic ones. Then you can render the mesh using the @ref Shaders::PhongGL shader like above, or use for example @ref Shaders::FlatGL3D or even @ref Shaders::MeshVisualizerGL3D with the same mesh reconfiguration. The unused attributes will be simply ignored. @snippet MagnumShaders-gl.cpp shaders-meshvisualizer The @ref MeshTools::compile() utility configures meshes using generic vertex attribute definitions to make them usable with any builtin shader. Besides vertex attributes, @ref Shaders::GenericGL contains generic definitions for framebuffer outputs as well --- in many cases a shader has just one (color) output, but some shaders such as @ref Shaders::FlatGL or @ref Shaders::PhongGL offer an object ID output as well. A setup equivalent to what's done in Flat shader's @ref Shaders-FlatGL-object-id but using the generic definitions would look like this: @snippet MagnumShaders-gl.cpp shaders-generic-object-id */ }