I have an OpenGL program where the shader sources are defined using an array. Vertex and fragment shader are stored in the single file and compiled based on a macro definition. For the simplicity a shader file is defined as const char*
in the following example:
const char* ver = "#version 120\n";
const char* vsDef = "#define VERTEX_SHADER\n";
const char* fsDef = "#define FRAGMENT_SHADER\n";
const char* body =
"#line 0 1\n"
"#ifdef VERTEX_SHADER\n"
"void main()\n"
"{\n"
" gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n"
"}\n"
"#endif\n"
"#ifdef FRAGMENT_SHADER\n"
"void main()\n"
"{\n"
" gl_FragData[0] = vec4(0.0, 1.0, 1.0, 1.0);\n"
"}\n"
"#endif\n";
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
const char* src[3] = { ver, vsDef, body };
int length[3] = { strlen(ver), strlen(vsDef), strlen(body) };
glShaderSource(vertexShader, 3, src, length);
glCompileShader(vertexShader);
//next compile fragment shader, link program etc.
How the line directive - #line 0 1
works in the above example ? Does it has something to do with defining shader source using an array and a shader logs which can be read using glGetShaderInfoLog
?
Form GLSL Preprocessor directives section:
#line directive
The
#line
directive allows you to change the current__FILE__
and__LINE__
values, and is different from C. It has the forms:#line line #line line source-string-number
The line after the
#line
directive in the source will be set to the given line number. For example, if you have "#line 4" on one line and "error" on the next, then "error" will be on line 4 if it is an error. The#line
directive does not support source files as in C.
Plus one (quite important) remark mentioned in the following section:
GLSL defines a number of macros.
__FILE__
is not a filename; it is a decimal integer representing which string in the list of strings given to the shader.__LINE__
is the line number.
How the line directive - #line 0 1 works in the above example ?
It works pretty much like the same-named directive from C/C++ language. I.e. from the line followed by this directive, the value __LINE__
and __FILE__
macroses are manually changed, and the track of line number and file name remains altered until end of the translation unit. E.g.
// file foo.cpp
int logLines() {
std::cout << __FILE__ << ":" << __LINE__ << std::endl; // foo.cpp:3
#line 256 "bar.cpp"
std::cout << __FILE__ << ":" << __LINE__ << std::endl; // bar.cpp:256
// Pay attention, that effect remains active from this point forward. See the output of the next line
std::cout << __FILE__ << ":" << __LINE__ << std::endl; // bar.cpp:258
return 0;
}
This, however, is predominantly debugging information, it does not alter the code and (to my knowledge) is used purely by compilers to report the proper line in the source in case this text was changed after optimisation/preprocessing/obfuscation. So as a consumer of a programming language, you don't usually use this directive, but it helps to understand where a problem is if it happens.
In case of GLSL the situation is slightly different: it doesn't has the notions of files, but instead use unnamed source strings (referred by numbers). As you can see from the previous section, in place of the second argument this directive accepts so-called source-string-number
. I explain what this number is and where you, as a GLSL programmer, can get use of it in the following sections. Also, unlike C #line
directive, and despite what the GLSL documentation says, the GLSL #line
directive seems to apply changes for the line where the directive itself is located, not the line which follows it, if you set #line 4
, consider the next line to be 5'th, not 4'th.
Does it has something to do with defining shader source using an array...
Not exactly. No matter which way you pass the source code to the glShaderSource
function, all parts get concatenated into a single source-string, with the same source-string-number
. The following example:
const GLchar* ver = "#version 120\n";
const GLchar* body =
"attribute vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\n";
const GLchar* sources[] { ver, body };
glShaderSource(shader, 2, sources, nullptr);
Is considered identical to this:
const GLchar* body =
"#version 120\n"
"attribute vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\n";
glShaderSource(shader, 1, &body, nullptr);
The reason to split the source code sometimes is just to reuse some parts of GLSL code.
Does it has something to do with ... a shader logs which can be read using
glGetShaderInfoLog
?
This is what it's all about essentially. If you have the source code split between different strings it gets more complicated to locate the problematic line of code. Let's remove the #line
directive and add some ill-formed text in the following shader:
1 #ifdef VERTEX_SHADER
2 attribute vec3 aPos;
3 void main()
4 {
5 error; // ill-formed part
6 gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
7 }
8 #endif
9 #ifdef FRAGMENT_SHADER
10 void main()
11 {
12 gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
13 }
14 #endif
Like in your example, I complement it with two other strings, which define shader type and GLSL version:
const GLchar* ver = "#version 120\n";
const GLchar* vsDef = "#define VERTEX_SHADER\n";
const GLchar* sources[] { ver, vsDef, body };
glShaderSource(shader, 3, sources, nullptr);
glCompileShader(shader);
If at this point you read the compilation error message, it will look something like this:
ERROR: 0:7: Use of undeclared identifier 'error'
The numbers next to ERROR:
denote where the ill-formed part of GLSL code is located. The latter number 7
is the code line, while the former 0
is exactly what we called source-string-number
earlier. If you now go the content of body
and try to examine 7'th line, you will not find anything related, because, as we discussed earlier, all three strings are just concatenated in a single one, and in order to see where exactly the error is now, we have to concatenate the sources ourselves, so it looks like that:
1 #version 120
2 #define VERTEX_SHADER
3 #ifdef VERTEX_SHADER
4 attribute vec3 aPos;
5 void main()
6 {
7 error; // ill-formed part
...
However, if you alter the line manually (like it's done in your example):
#line 0 1
1 #ifdef VERTEX_SHADER
2 attribute vec3 aPos;
3 void main()
4 {
5 error; // ill-formed part
...
you don't even have to concatenate the strings, the log will report it relative to the beginning of the body
string:
ERROR: 1:5: Use of undeclared identifier 'error'
That might not look like a big deal, if you work with just a one big string, which contains 99% of the GLSL code, but if your code is split between dozens of strings, which can be composed in arbitrary order, this technique comes quite handy. The possibility to change the source-string-number
(the second argument of the directive) is also very useful, because you can assign different number to each piece of code and just look through your strings by this line directive. E.g. for the error example above, i can quickly search source code which contains #line 0 1
, and locate which string the error is in (if i have another big chunk of code under another string, i can put #line 0 2
to differentiate it from other parts of GLSL code)