Generating pipelines, attaching shaders: working code anyone?

Greetings:
I am using core 4.3. Masochistic maybe but your help would be appreciated with the following:-)

I have two different geometric objects that need to be transformed differently. I know I can do this in one vertex shader with a conditional on a uniform.

But I am curious how to do this by setting up two separate pipelines, one with a VS to handle obj1 and the other a VS to handle obj2. The fragment shader would be common pass-through. So, I would bind pipeline1 prior to drawing obj1 and pipeline2 prior to drawing obj2.

In a short section on separate shader objects the red book 8th ed. indicates use of
glCreateShaderProgramv() to make a sharable program for a shader, then glGenProgramPipelines() and glBindProgramPipeline() to create/manage pipelines and
glUseProgramStages() to attach shader stages to the pipeline.

Then there’s a bit about “The built-in gl_PerVertex block must be redeclared in shaders” which I don’t understand at all.

I would really appreciate it if someone kindly shares simple working code illustrating the above or explains the process step by step. (The RB site has nothing yet and google didn’t turn up much code.)

Thanks in advance,
Sam

Care to elaborate why? O_o

I have to admit, I wasn’t aware of it either but the GLSL spec states:

EDIT: I was wrong before. Having tried it myself using separable programs, I can conclude that at least on AMD (and IIRC groovounet also stated on his website that NV is picky here too), you need to redeclare gl_PerVertex. Please refer to this thread for an exhaustive and comparably exhausting bug report.

Yes, when using separable programs you have to declare gl_PerVertex block. NV driver also (correctly) requires it.

Anyway, as requested, here’s some working, albeit horribly useless, code:


GLuint pipeline;

void createProgramPipeline()
{
    gl::GenProgramPipelines(1, &pipeline);
    GLuint vShader = gl::CreateShaderProgramv(gl::VERTEX_SHADER, 1, &vSrc);
    GLuint gShader = gl::CreateShaderProgramv(gl::GEOMETRY_SHADER, 1, &geomSrc);
    GLuint fShader = gl::CreateShaderProgramv(gl::FRAGMENT_SHADER, 1, &fSrc);

    gl::UseProgramStages(pipeline, gl::VERTEX_SHADER_BIT, vShader);
    gl::UseProgramStages(pipeline, gl::GEOMETRY_SHADER_BIT, gShader);
    gl::UseProgramStages(pipeline, gl::FRAGMENT_SHADER_BIT, fShader);

    // if you don't use explicit uniform locations you still need to query the location
    GLint loc = gl::GetUniformLocation(fShader, "color");

    // example use of DSA-style uniform interface
    gl::ProgramUniform4f(fShader, loc, 1.f, 0.f, 0.f, 1.f);

    // if you want to use classic uniform calls, you first need to select the correct separate program
    // gl::ActiveShaderProgram(pipeline, fShader);
    // gl::Uniform4f(loc, 1.f, 0.f, 0.f, 1.f);

    GLchar log[2048];
    gl::GetProgramInfoLog(vShader, 2048, NULL, log);
    printLog (log);
    gl::GetProgramInfoLog(gShader, 2048, NULL, log);
    printLog (log);
    gl::GetProgramInfoLog(fShader, 2048, NULL, log);
    printLog (log);
    gl::GetProgramPipelineInfoLog(pipeline, 2048, NULL, log);
    printLog (log);

    // to use the pipeline call the following - note that if glUseProgram() was called with a valid program object before, BindProgramPipeline has NO effect!
    // call glUseProgram(0) if you're unsure
    gl::BindProgramPipeline(pipeline);

}

Two things:

[ul]
[li]don’t get distracted by the use of the gl namespace. It’s because I’m using Alfonse’s glLoadGen which can generate namespace encapsulated code. [/li][li]note that there are no gl::BindAttribLocation or gl::BindFragDataLocation calls anywhere. Why? Simple: You cannot call the functions anymore because you don’t explicitly link a seperable program - it’s compiled, linked and validated automatically when calling CreateShaderProgramv()! However, BindFragDataLocation and BindAttribLocation need to be called before linking or else the calls will have no effect. The solution: explicit attribute locations like so:[/li]


// vertex shader
layout(location = 0) in vec 4 whatever; // the index of the generic attribute [i]whatever [/i]is now 0

// fragment shader
layout(location = 0) out vec4 RenderTarget1; // output FragColor written to draw buffer 0 - COLOR_ATTACHMENT0
layout(location = 1) out vec4 RenderTarget2; // output FragColor written to draw buffer 1 - possibly COLOR_ATTACHMENT1

[/ul]

EDIT: While the above is true, i.e. using explicit attrib locations instead of glBind{Attrib|FragData}Location(), I forgot to mention one thing: If not specified with the aforementioned call or explicitly defined in the shader, the linker will assign locations which can be retrieved with glGet{Attrib|FragData}Location(). However, since ARB_separate_shader_objects depends on ARB_explicit_attrib_location you should do it the new way and just be explicit. The SGI-inspired(or mandated) query stuff was and is simply cumbersome.

EDIT2: As I understand it, you could theoretically avoid the above “shortcomings” of using glCreateShaderProgramv() by simply rewriting the equivalent code section to include the binding of attrib locations using the API. All in all, the only difference compared to the normal flow (and the bit that makes the separable linkage possible) is the addition of

glProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE);

before actually linking the program. One could ammend the code by doing stuff like this:


glAttachShader(program, shader);

// bind attribs here
glBindAttribLocation(....)/glBindFragDataLocation(....)
....

glProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE);
glLinkProgram(program);
glDetachShader(program, shader);

At least I theorize that this should work. Issue #15 in ARB_separate_shader_objects addresses this as well, but seems to use the API from EXT_seperate_shader_objects - which doesn’t match the core API.
However, I’m still of the opinion that the benefit of using glCreateShaderProgramv() and explicit locations outweighs the benefit of assigning attrib locations via API.

Thanks a million, Thokra.
I am going to try then to write a minimal program setting up a pipeline to draw a triangle. If it doesn’t work I hope you won’t mind taking a look.

If it doesn’t work I hope you won’t mind taking a look.

No i don’t. :slight_smile:

Bump because of two rather important edits.

Hi Thokra:
Really appreciate if you took a look. I am working in Win7/Visual C++ 2010. The code compiles ok but I just get a blank screen. The triangle is not drawn.
Thanks,
Sam

Main program


////////////////////////////////////////////////////          
// trianglePipeline.cpp
////////////////////////////////////////////////////

#include <iostream>
#include <fstream>

#  include <GL/glew.h>
#  include <GL/freeglut.h>
#  include <GL/glext.h>
#pragma comment(lib, "glew32.lib") 

struct Vertex
{
   float coords[4];
   float colors[4];
};

struct Matrix4x4
{
   float entries[16];
};

static const Matrix4x4 IDENTITY_MATRIX4x4 = 
{
   {
      1.0, 0.0, 0.0, 0.0,
      0.0, 1.0, 0.0, 0.0,
      0.0, 0.0, 1.0, 0.0,
      0.0, 0.0, 0.0, 1.0
   }
};

static Matrix4x4
   MVmatrix,
   projMatrix;

static unsigned int
   vShaderProgId,
   fShaderProgId,
   MVmatrixUnifLoc,
   projMatrixUnifLoc,
   pipeline,
   buffer, 
   vao;    

Vertex triangleVertices[] = // Triangle vertex data: { {Coordinates 4-vector}, {Color 4-vector} } 
{
   { { 20.0, 20.0, 0.0, 1.0 }, { 0.0, 0.0, 0.0, 1.0 } },
   { { 80.0, 20.0, 0.0, 1.0 }, { 0.0, 0.0, 0.0, 1.0 } },
   { { 20.0, 80.0, 0.0, 1.0 }, { 0.0, 0.0, 0.0, 1.0 } }
};

//Function to read a text file.
char* readTextFile(char* aTextFile)
{
   FILE* filePointer = fopen(aTextFile, "rb");	
   char* content = NULL;
   long numVal = 0;

   fseek(filePointer, 0L, SEEK_END);
   numVal = ftell(filePointer);
   fseek(filePointer, 0L, SEEK_SET);
   content = (char*) malloc((numVal+1) * sizeof(char)); 
   fread(content, 1, numVal, filePointer);
   content[numVal] = '\0';
   fclose(filePointer);
   return content;
}

// Initialization routine.
void setup(void)
{
   glGenProgramPipelines(1, &pipeline);

   char* vShaderSrc = readTextFile("vShader.glsl");
   char* fShaderSrc = readTextFile("fShader.glsl");
   
   vShaderProgId = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, (const char**) &vShaderSrc);
   fShaderProgId = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, (const char**) &fShaderSrc); 

   glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, vShaderProgId);
   glUseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, fShaderProgId);

   glClearColor(1.0, 1.0, 1.0, 0.0);

   glGenVertexArrays(1, &vao);
   glBindVertexArray(vao);

   glGenBuffers(1, &buffer);
   glBindBuffer(GL_ARRAY_BUFFER, buffer);
   glBufferData(GL_ARRAY_BUFFER, sizeof(triangleVertices), triangleVertices, GL_STATIC_DRAW);

   glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(triangleVertices[0]), 0);
   glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(triangleVertices[0]), (GLvoid*)sizeof(triangleVertices[0].coords));

   glEnableVertexAttribArray(0);
   glEnableVertexAttribArray(1);

   Matrix4x4 MVmatrix = IDENTITY_MATRIX4x4;
   MVmatrixUnifLoc = glGetUniformLocation(vShaderProgId,"MVmatrix"); 
   glProgramUniformMatrix4fv(vShaderProgId, MVmatrixUnifLoc, 1, GL_TRUE, MVmatrix.entries);

   Matrix4x4 projMatrix = 
   {
      {
         0.02, 0.0,  0.0, -1.0,
         0.0,  0.02, 0.0, -1.0,
         0.0,  0.0, -1.0,  0.0,
         0.0,  0.0,  0.0,  1.0
      }
   };
   projMatrixUnifLoc = glGetUniformLocation(vShaderProgId,"projMatrix"); 
   glProgramUniformMatrix4fv(vShaderProgId, projMatrixUnifLoc, 1, GL_TRUE, projMatrix.entries);
}

// Drawing routine.
void drawScene(void)
{
   glBindProgramPipeline(pipeline);
   glClear(GL_COLOR_BUFFER_BIT);
   
   glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);
   
   glFlush();
}

// OpenGL window reshape routine.
void resize(int w, int h)
{
   glViewport(0, 0, w, h);
}

// Main routine.
int main(int argc, char* argv[])
{	
   glutInit(&argc, argv);

   glutInitContextVersion(4, 3);
   glutInitContextProfile(GLUT_CORE_PROFILE);
   
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize(500, 500);
   glutInitWindowPosition(100, 100); 
   glutCreateWindow("trianglePipeline");
   glutDisplayFunc(drawScene);
   glutReshapeFunc(resize);
   
   glewExperimental = GL_TRUE;
   glewInit();

   setup();

   glutMainLoop();
}

Vertex shader:

#version 430 core

layout(location=0) in vec4 vertexCoordsIn;
layout(location=1) in vec4 vertexColorsIn;
out vec4 colorsExport;

uniform mat4 MVmatrix;
uniform mat4 projMatrix;
	
void main(void)
{
   gl_Position = projMat * MVmatrix * vertexCoordsIn;
   colorsExport = vertexColorsIn;
}

Fragment shader:

#version 430 core

in vec4 colorsExport;
out vec4 colorsOut;

void main(void)
{
   colorsOut = colorsExport;
}

The API code looks good at first glance. The shaders do as well - for a monolithic program at least. You didn’t redefine gl_PerVertex in the vertex shader, however. First, please check the program logs to verify that no compilation/linking error happened. I suspect it did. If redeclaration doesn’t work out, I’d try it with a monolithic program to confirm the rest of the code is proper. You can redeclare gl_PerVertex for that purpose just the same. gl_Position will be all you need and matching is of no concern since there aren’t subsequent pipeline stages that read any other value (other than the fixed stages which will use gl_Position).

Hi Thokra:
The fragment shader and pipeline don’t generate errors. The only one is from the vertex shader as below:

Vertex info

0(12) : error C7592: ARB_separate_shader_objects requires built-in block gl_PerVertex to be redeclared before accesing its members

So, I do need to redeclare gl_PerVertex. Sorry, but I have to ask: how does one do this exactly? If you could tell me what the lines are I have to include in my vertex and fragment shaders it would really help.

Thanks again,
Sam

// in the vertex shader
out gl_PerVertex
{
  vec4 gl_Position;
  // you don't need to redeclare gl_PointSize and gl_ClipDistance[] if you don't use write to them
};

It works! Thanks a million.
Can I ask though why this redeclaration is needed: after all gl_PerVertex is a built-in so why does a shader need to be “told” about it again? And one more: if it’s out in the VS, why don’t we need to declare it as in in the FS?

And one more: if it’s out in the VS, why don’t we need to declare it as in in the FS?

Because gl_PerVertex is not a built-in input interface block in the fragment language.

Can I ask though why this redeclaration is needed: after all gl_PerVertex is a built-in so why does a shader need to be “told” about it again?

Actually, I can only tell you that you must do it or a spec conforming implemetation will complain. The rules on other interfaces are more relaxed and mismatches will most of the time “only” result in undefined behavior. Ironically, AFAIK, writing to any member of gl_PerVertex is optional in any shader stage. So, I’m afraid I can’t tell you why the spec is that strict when it come to separable programs. Maybe someone else can.

Thokra, thank you again.