During OpenGL initialization, the program is supposed to do something like:
<Get Shader Source Code> <Create Shader> <Attach Source Code To Shader> <Compile Shader>
Getting source code could be as simple as putting it in a string like: (Example taken from SuperBible, 6th Edition)
static const char * vs_source[] = { "#version 420 core \n" " \n" "void main(void) \n" "{ \n" " gl_Position = vec4(0.0, 0.0, 0.0, 1.0); \n" "} \n" };
The problem is that it is hard to edit, debug and maintain GLSL shaders directly in a string. So getting the source code in a string from a file is easier for development:
std::ifstream vertexShaderFile("vertex.glsl"); std::ostringstream vertexBuffer; vertexBuffer << vertexShaderFile.rdbuf(); std::string vertexBufferStr = vertexBuffer.str(); // Warning: safe only until vertexBufferStr is destroyed or modified const GLchar *vertexSource = vertexBufferStr.c_str();
The problem now is how to ship the shaders with your program? Indeed, shipping source code with your application may be a problem. OpenGL supports "pre-compiled binary shaders" but the Open Wiki states that:
Program binary formats are not intended to be transmitted. It is not reasonable to expect different hardware vendors to accept the same binary formats. It is not reasonable to expect different hardware from the same vendor to accept the same binary formats. [...]
How to practically ship GLSL shaders with your C++ software?
GLSL is the shading language for OpenGL, therefore GLSL code is executed on the GPU. Why GLSL is better at rendering than normal c/c++? It's not better or worse; you cannot use one for the other. You cannot just throw random C-code at a GPU as part of the rendering pipeline.
The Vulkan SDK includes libshaderc, which is a library to compile GLSL code to SPIR-V from within your program.
One way to speed up GLSL code, is by marking some variables constant at compile-time. This way the compiler may optimize code (e.g. unroll loops) and remove unused code (e.g. if hard shadows are disabled). The drawback is that changing these constant variables requires that the GLSL code is compiled again.
There is just "store them directly in the executable" or "store them in (a) separate file(s)", with nothing in-between. If you want a self-contained executable, putting them into the binary is a good idea. Note that you can add them as resources or tweak your build system to embed the shader strings from separate development files into source files to make development easier (with the possible addition of being able to directly load the separate files in development builds).
Why do you think shipping the shader sources would be a problem? There is simply no other way in the GL. The precompiled binaries are only useful for caching the compilation results on the target machine. With the fast advances of GPU technology, and changing GPU architectures, and different vendors with totally incompatible ISAs, precompiled shader binaries do not make sense at all.
Note that putting your shader sources in the executeable does not "protect" them, even if you encrypt them. A user can still hook into the GL library and intercept the sources you specify to the GL. And the GL debuggers out there do exactly that.
UPDATE 2016
At SIGGRAPH 2016, the OpenGL Architecture Review Board released the GL_ARB_gl_spirv
extension. This will allow a GL inmplementation to use the SPIRV binary intermediate language. This has some potential benefits:
With that scheme, GL is becoming more similar to D3D and Vulkan in that regard. However, it doesn't change the greater picture. The SPIRV bytecode can still be intercepted, disassembled and decompiled. It does make reverse-engineering a little bit harder, but not by much actually. In a shader, you usually can't afford extensive obfuscuation measures, since that dramatically reduces performance - which is contrary to what the shaders are for.
Also keep in mind that this extension is not widely available right now (autumn 2016). And Apple has stopped supporting GL after 4.1, so this extension will probably never come to OSX.
MINOR UPDATE 2017
GL_ARB_gl_spirv
is now official core feature of OpenGL 4.6, so that we can expect growing adoption rate for this feature, but it doesn't change the bigger picture by much.
With c++11, you can also use the new feature of raw string literals. Put this source code in a separate file named shader.vs
:
R"( #version 420 core void main(void) { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); } )"
and then import it as a string like this:
const std::string vs_source = #include "shader.vs" ;
The advantage is that its easy to maintain and debug, and you get correct line numbers in case of errors from the OpenGL shader compiler. And you still don't need to ship separate shaders.
The only disadvantage I can see is the added lines on the top and bottom of the file (R")
and )"
) and the syntax that is a little bit strange for getting the string into C++ code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With