How to Practically Ship GLSL Shaders with your C++ Software

Viewed 40606

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?

11 Answers

A suggestion:

In your program, put the shader in:

const char shader_code = {
#include "shader_code.data"
, 0x00};

In shader_code.data there should be the shader source code as a list o hex numbers separated by commas. These files should be created before compilation using your shader code written normally in a file. In Linux I would put instructions at Makefile to run the code:

cat shader_code.glsl | xxd -i > shader_code.data

I don`t know if that will work, but you could embed the .vs file into your executable with binutils like program like g2bin, and you can declare your shader programs as externals then you access them as normal resources embedded in the executable. See qrc in Qt, or you can view my small program for embedding stuff in executables here: https://github.com/heatblazer/binutil which is invoked as pre-build command to the IDE.

In C99/C11 you can do it in 2 simple steps.

## Bash build script:

## STEP #1: Convert your [C99/C11] code to GLSL by quoting it:

    awk 'NF { print "\""$0"\\""n""\""}' GLSL_AS_C99.C11 > QUOTED.TXT

## STEP #2: Compile your project:

    gcc -x c -c MY_PROJECT_FILE.C11 -o object_file.o -std=c11 -m64
    gcc -o EXE.exe object_file.o
    rm object_file.o
    ./EXE.exe
    rm EXE.exe

Yes. There is more to it. You have to write your C99 in a generic style that will also compile as GLSL. For example:

#ifdef THIS_IS_BEING_COMPILED_AS_OPEN_GL_SHADER_CODE
    #define I32 int
#endif
#ifdef THIS_IS_BEING_COMPILED_AS_C99_CODE
    #define I32 uint32_t
#endif

Code written in such a way in C99 can be cut and pasted into GLSL shader code with no problems. It also allows you to unit test your GLSL shader code. To include the code that was stringified by the AWK command do something like this:

//:AAC2020_PAINT5D_DEFAULT_001:==============================://
const char* AAC2020_PAINT5D_DEFAULT_001=( //:////////////////://
//://////////////////////////////////////////////////////////://
"#version 330 core                           \n"//://////////://
"#define AAC2020_MACRO_THIS_IS_OPEN_GL (1)   \n"//://////////://
//://////////////////////////////////////////////////////////://
//|Everything Below Is Cut+Pasted From       |////://////////://
//|The C99 File: P5D_OGL._                   |////://////////://
//://////////////////////////////////////////////////////////://

    #include "../QUOTED.TXT"
                                                
); //:///////////////////////////////////////////////////////://
//:==============================:AAC2020_PAINT5D_DEFAULT_001://

If you are familiar with C code and bash script. That should be enough to explain it. But if you require more explanation I filmed a 30 minute demonstration and explanation video on youtube.

https://www.youtube.com/watch?v=kQfSL4kv5k0&list=PLN4rUakF78aCdRxjMU8_JBGAKIrtt_7N5&index=115

If you would prefer WORKING CODE... Here is my game engine build that uses this system. Open up the AAC2020.SH build script to find the "awk" command and work backwards from there.

https://github.com/KanjiCoder/AAC2020

Other files of specific interest to the problem at hand are:

  1. P5D1OGL.FRA._ <-- The C99 source code that can be ran as GLSL and unit tested
  2. P5D1OGL.FRA.STRING._ <-- P5D1OGL.FRA._ quoted by the awk command.
  3. P5D_001._ <-- P5D1OGL.FRA.STRING._ #included into a const char* to embed into exe
  4. POLYOGL.D._ <-- OpenGL Polyfills .D (data / structs)
  5. POLYOGL.F._ <-- OpenGL Polyfills .F (functions)

Alternatively, if you tune into my twitch stream and request an in-person overview of how I do this, I can give you a live demo and further explanation.

www.twitch.com/kanjicoder

You can also email me at: HeavyMetalCookies@Gmail.com I am currently 35 and the year is 2021. If I don't reply it means I am either dead , too famous to answer, or both. I'll leave it as an exercise to the reader to figure out which one of those it is.

-John Mark

Related