libgpcogl - shader programming

Half of the shader programming is really just raw OpenGL shader programming, using the official Shading Language. Most notably: functions, function calls, basic data types, vectors (e.g. vec2, ivec4) type conversions. Target version is 130 or higher - but going higher always narrows the set of GPUs the code would run on.

The other half is a collection of C macros provided by gpcogl to automate writing some parts of the shader program. The intention is to hide some of the OpenGL details (such as "uniforms" and "samplers") and provide the same, simpler, array based API and terminology that is used on the C API side.

This document describes these macros.

Storing the shader program, line numbering

Because of the C macros, shader programs shall be stored as const char * strings in the C source. Any of the GPCOGL_ macros used in the shader program will change line numbering (starting from the end of that line) from the shader program's local line numbers to the hosting C source file's line numbers. This both helps debugging shader programs and resolves the problem that some of these macros have to insert more then one shader program line.

Program header, version

#define GPCOGL_SHADER_PROG(minver) ...

A shader program is introduced by using the GPCOGL_SHADER_PROG() macro in its first non-empty line. This macro prepares the environment so that the shader language version and required extensions are communicated to the compiler.

minver is the minimum GLSL version required by the shader program. It should normally be 130.

Program output array

#define GPCOGL_OUT_1_FLOAT32(name)    // in shader: float; in C: float
#define GPCOGL_OUT_2_FLOAT32(name)    // in shader: vec2;  in C: 2*float
#define GPCOGL_OUT_3_FLOAT32(name)    // in shader: vec3;  in C: 3*float
#define GPCOGL_OUT_4_FLOAT32(name)    // in shader: vec4;  in C: 4*float
#define GPCOGL_OUT_1_INT32(name)      // in shader: int;   in C: int32_t
#define GPCOGL_OUT_2_INT32(name)      // in shader: ivec2; in C: 2*int32_t
#define GPCOGL_OUT_3_INT32(name)      // in shader: ivec3; in C: 3*int32_t
#define GPCOGL_OUT_4_INT32(name)      // in shader: ivec4; in C: 4*int32_t
#define GPCOGL_OUT_1_UINT32(name)     // in shader: uint;  in C: uint32_t
#define GPCOGL_OUT_2_UINT32(name)     // in shader: uvec2; in C: 2*uint32_t
#define GPCOGL_OUT_3_UINT32(name)     // in shader: uvec3; in C: 3*uint32_t
#define GPCOGL_OUT_4_UINT32(name)     // in shader: uvec4; in C: 4*uint32_t

Every shader program must have exactly one output array declared using one of the GPCOGL_OUT_* macros, depending on array type. The name and array type must match on C and shader side. name is then used as a global variable in the shader program.

Program input array(s)

#define GPCOGL_IN_1_FLOAT32(name)     // in shader: float; in C: float
#define GPCOGL_IN_2_FLOAT32(name)     // in shader: vec2;  in C: 2*float
#define GPCOGL_IN_3_FLOAT32(name)     // in shader: vec3;  in C: 3*float
#define GPCOGL_IN_4_FLOAT32(name)     // in shader: vec4;  in C: 4*float
#define GPCOGL_IN_1_INT32(name)       // in shader: int;   in C: int32_t
#define GPCOGL_IN_2_INT32(name)       // in shader: ivec2; in C: 2*int32_t
#define GPCOGL_IN_3_INT32(name)       // in shader: ivec3; in C: 3*int32_t
#define GPCOGL_IN_4_INT32(name)       // in shader: ivec4; in C: 4*int32_t
#define GPCOGL_IN_1_UINT32(name)      // in shader: uint;  in C: uint32_t
#define GPCOGL_IN_2_UINT32(name)      // in shader: uvec2; in C: 2*uint32_t
#define GPCOGL_IN_3_UINT32(name)      // in shader: uvec3; in C: 3*uint32_t
#define GPCOGL_IN_4_UINT32(name)      // in shader: uvec4; in C: 4*uint32_t

A shader program may have zero or more input arrays declared using the GPCOGL_IN_* macros, depending on array type. The name and array type must match on C and shader side. name is then used as a global read-only variable in the shader program.

However the name variable is inconvenient to use directly in the shader program because of it's type (it's a sampler). Thus a shader macro is also defined for each input, called getxy_name(x,y); this returns the cell of the input array named by name at x;y coord (indexed from 0). Return type matches array cell type.

Current (output) x and y

#define GPCOGL_OUT_COORDS(xname, yname)  ...

This macro should be placed as the first line of the shader function main(), wtih suitable variable names for xname and yname (typically simply x and y). The macro creates int xname and int yname (with the names passed) and fetches and assigns the x and y coordinate of the output array cell the current shader invocation is operating on.

(The reason these variables are ints and not uints is to make things like +1 and -1 easier to do; with uints one would need to do +1u or -1u to match types.)

Global constants

#define GPCOGL_CONST_1_FLOAT32(name)  // in shader: float; in C: float
#define GPCOGL_CONST_2_FLOAT32(name)  // in shader: vec2;  in C: 2*float
#define GPCOGL_CONST_3_FLOAT32(name)  // in shader: vec3;  in C: 3*float
#define GPCOGL_CONST_4_FLOAT32(name)  // in shader: vec4;  in C: 4*float
#define GPCOGL_CONST_1_INT32(name)    // in shader: int;   in C: int32_t
#define GPCOGL_CONST_2_INT32(name)    // in shader: ivec2; in C: 2*int32_t
#define GPCOGL_CONST_3_INT32(name)    // in shader: ivec3; in C: 3*int32_t
#define GPCOGL_CONST_4_INT32(name)    // in shader: ivec4; in C: 4*int32_t
#define GPCOGL_CONST_1_UINT32(name)   // in shader: uint;  in C: uint32_t
#define GPCOGL_CONST_2_UINT32(name)   // in shader: uvec2; in C: 2*uint32_t
#define GPCOGL_CONST_3_UINT32(name)   // in shader: uvec3; in C: 3*uint32_t
#define GPCOGL_CONST_4_UINT32(name)   // in shader: uvec4; in C: 4*uint32_t

A compute() call may set named global constants which are then bound to the same named global constants in the shader programs. Types also need to match on the C and shader side.

Such global constants can be used to pass on settings, parameters or scalar input values to shader programs. However, these are constant values throghout a compute() call: neither the C program nor the shader can change the value of these.