Meaning of “index” parameter in glEnableVertexAttribArray and (possibly) a bug in the OS X OpenGL implementation

依然范特西╮ 提交于 2021-02-08 04:48:47

问题


1) Do I understand correctly that to draw using vertex arrays or VBOs I need for all my attributes to either call glBindAttribLocation before the shader program linkage or call glGetAttribLocation after the shader program was successfully linked and then use the bound/obtained index in the glVertexAttribPointer and glEnableVertexAttribArray calls?

To be more specific: these three functions - glGetAttribLocation, glVertexAttribPointer and glEnableVertexAttribArray - they all have an input parameter named "index". Is it the same "index" for all the three? And is it the same thing as the one returned by glGetAttribLocation?

If yes: 2) I've been facing a problem on OS X, I described it here: https://stackoverflow.com/questions/28093919/using-default-attribute-location-doesnt-work-on-osx-osx-opengl-bug , but unfortunately didn't get any replies.

The problem is that depending on what attribute locations I bind to my attributes I do or do not see anything on the screen. I only see this behavior on my MacBook Pro with OS X 10.9.5; I've tried running the same code on Linux and Windows and it seems to work on those platforms independently from which locations are my attributes bound to.

Here is a code example (which is supposed to draw a red triangle on the screen) that exhibits the problem:

#include <iostream>
#include <GLFW/glfw3.h>

GLuint global_program_object;
GLint  global_position_location;
GLint  global_aspect_ratio_location;
GLuint global_buffer_names[1];

int LoadShader(GLenum type, const char *shader_source)
  {
  GLuint shader;
  GLint compiled;
  shader = glCreateShader(type);
  if (shader == 0)
    return 0;
  glShaderSource(shader, 1, &shader_source, NULL);
  glCompileShader(shader);
  glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
  if (!compiled)
    {
    GLint info_len = 0;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len);
    if (info_len > 1)
      {
      char* info_log = new char[info_len];
      glGetShaderInfoLog(shader, info_len, NULL, info_log);
      std::cout << "Error compiling shader" <<  info_log << std::endl;
      delete info_log;
      }
    glDeleteShader(shader);
    return 0; 
    }
  return shader; 
  }

int InitGL()
  {
  char vertex_shader_source[] =
    "attribute vec4  att_position;                       \n"
    "attribute float dummy;\n"
    "uniform   float uni_aspect_ratio;                   \n"
    "void main()                                         \n"
    "  {                                                 \n"
    "  vec4 test = att_position * dummy;\n"
    "  mat4 mat_projection =                             \n"
    "   mat4(1.0 / uni_aspect_ratio, 0.0,  0.0, 0.0,     \n"
    "                           0.0, 1.0,  0.0, 0.0,     \n"
    "                           0.0, 0.0, -1.0, 0.0,     \n"
    "                           0.0, 0.0,  0.0, 1.0);    \n"
    "  gl_Position = att_position;                       \n"
    "  gl_Position *= mat_projection;                    \n"
    "  }                                                 \n";

  char fragment_shader_source[] =
    "void main()                                \n"
    "  {                                        \n"
    "  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n"
    "  }                                        \n";

  GLuint vertex_shader;
  GLuint fragment_shader;
  GLuint program_object;
  GLint  linked;

  vertex_shader   = LoadShader(GL_VERTEX_SHADER  , vertex_shader_source  );
  fragment_shader = LoadShader(GL_FRAGMENT_SHADER, fragment_shader_source);

  program_object = glCreateProgram();
  if(program_object == 0)
    return 1;
  glAttachShader(program_object, vertex_shader  );
  glAttachShader(program_object, fragment_shader);

  // Here any index except 0 results in observing the black screen
  glBindAttribLocation(program_object, 1, "att_position");
  glLinkProgram(program_object);
  glGetProgramiv(program_object, GL_LINK_STATUS, &linked);
  if(!linked)
    {
    GLint info_len = 0;
    glGetProgramiv(program_object, GL_INFO_LOG_LENGTH, &info_len);
    if(info_len > 1)
      {
      char* info_log = new char[info_len];
      glGetProgramInfoLog(program_object, info_len, NULL, info_log);
      std::cout << "Error linking program" <<  info_log << std::endl;
      delete info_log; 
      }
    glDeleteProgram(program_object);
    return 1;
    }
  global_program_object = program_object;
  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  glUseProgram(global_program_object);
  global_position_location     = glGetAttribLocation (global_program_object, "att_position");
  global_aspect_ratio_location = glGetUniformLocation(global_program_object, "uni_aspect_ratio");

  GLfloat vertices[] = {-0.5f, -0.5f, 0.0f,
                         0.5f, -0.5f, 0.0f,
                         0.0f,  0.5f, 0.0f};

  glGenBuffers(1, global_buffer_names);
  glBindBuffer(GL_ARRAY_BUFFER, global_buffer_names[0]);
  glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 9, vertices, GL_STATIC_DRAW);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  return 0;
  }

void Render()
  {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  glUseProgram(global_program_object);
  glBindBuffer(GL_ARRAY_BUFFER, global_buffer_names[0]);
  glVertexAttribPointer(global_position_location, 3, GL_FLOAT, GL_FALSE, 0, 0);
  glEnableVertexAttribArray(global_position_location);
  glDrawArrays(GL_TRIANGLES, 0, 3);
  glDisableVertexAttribArray(global_position_location);
  glUseProgram(0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  }

void FreeGL()
  {
  glDeleteBuffers(1, global_buffer_names);
  glDeleteProgram(global_program_object);
  }

void SetViewport(int width, int height)
  {
  glViewport(0, 0, width, height);
  glUseProgram(global_program_object);
  glUniform1f(global_aspect_ratio_location, static_cast<GLfloat>(width) / static_cast<GLfloat>(height));
  }


int main(void)
  {
  GLFWwindow* window;
  if (!glfwInit())
    return -1;
  window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
  if (!window)
    {
    glfwTerminate();
    return -1;
    }
  glfwMakeContextCurrent(window);
  InitGL();

  // Double the resolution to correctly draw with Retina display
  SetViewport(1280, 960);
  while (!glfwWindowShouldClose(window))
    {
    Render();
    glfwSwapBuffers(window);
    glfwPollEvents();
    }
  FreeGL();
  glfwTerminate();
  return 0;
  }

Does this look like a bug to you? Can anyone reproduce it? If it's a bug where should I report it?

P.S. I've also tried SDL instead of GLFW, the behavior is the same...


回答1:


The behavior you see is actually correct as per the spec, and MacOSX has something to do with this, but only in a very indirect way.

To answer question 1) first: You are basically correct. With modern GLSL (>=3.30), you can also specifiy the desired index via the layout(location=...) qualifier directly in the shader code, instead of using glBindAttribLocation(), but that is only a side note.

The problem you are facing is that you are using a legacy GL context. You do not specify a desired version, so you will get maximum compatibility to the old way. Now on windows, you are very likely to get a compatibility profile of the highest version supported by the implementation (typically GL3.x or GL4.x on non-ancient GPUs).

However, on OSX, you are limited to at most GL2.1. And this is where the crux lies: your code is invalid in GL2.x. To explain this, I have to go back in GL history. In the beginning, there was the immediate mode, so you did draw by

glBegin(primType);
glColor3f(r,g,b);
glVertex3f(x,y,z);
[...]
glColor3f(r,g,b);
glVertex3f(x,y,z);
glEnd();

Note that the glVertex call is what actually creates a vertex. All other per-vertex attributes are basically some current vertex state which can be set any time, but calling glVertex will take all of those current attributes together with the position to form the vertex which is fed to the pipeline.

Now when vertex arrays were added, we got functions like glVertexPointer(), glColorPointer() and so on, and each attribute array could be enabled or disabled separately via glEnableClientState(). The array-based draw calls are actually defined in terms of the immediate mode in the OpenGL 2.1 specification as glDrawArrays(GLenum mode, GLint first, GLsizei count) being equivalent to

glBegin(mode);
for (i=0; i<count; i++)
    ArrayElement(first + i);
glEnd();

with ArrayElement(i) being defined (this one is derived from the wording of theGL 1.5 spec):

if ( normal_array_enabled )
    Normal3...( <i-th normal value> );
[...] // similiar for all other bultin attribs
if ( vertex_array_enabled)
    Vertex...( <i-th vertex value> );

This definition has some sublte consequence: You must have the GL_VERTEX_ARRAY attribute array enabled, otherwise nothing will be drawn, since no equivalent of glVertex calls are generated.

Now when the generic attributes were added in GL2.0, a special guarantee was made: generic attribute 0 is aliasing the builtin glVertex attribute - so both can be used interchangeably, in immediate mode as well as in arrays. So glVertexAttrib3f(0,x,y,z) "creates" a vertex the same way glVertex3f(x,y,z) would have. And using an array with glEnableVertexAttribArray(0) is as good as glEnableClientState(GL_VERTEX_ARRAY).

In GL 2.1, the ArrayElement(i) function now looks as follows:

if ( normal_array_enabled )
    Normal3...( <i-th normal value> );
[...] // similiar for all other bultin attribs
for (a=1; a<max_attribs; a++) {
    if ( generic_attrib_a_enabled )
        glVertexAttrib...(a, <i-th value of attrib a> );
}
if ( generic_attrib_0_enabled)
    glVertexAttrib...(0, <i-th value of attrib 0> );
else if ( vertex_array_enabled)
    Vertex...( <i-th vertex value> );

Now this is what happens to you. You absolutely need attribute 0 (or the old GL_VERTEX_ARRAY attribute) to be enabled for this to generate any vertices for the pipeline.

Note that it should be possible in theory to just enable attribute 0, no matter if it is used in the shader or not. You should just make sure that the corresponding attrib pointer pionts to valid memory, to be 100% safe. So you simply could check if your attribute index 0 is used, and if not, just set the same pointer as attribute 0 as you did for your real attribute, and the GL should be happy. But I haven't tried this.

In more modern GL, these requirements are not there anymore, and drawing without attribute 0 will work as intended, and that is what you saw on those other systems. Maybe you should consider switching to modern GL, say >= 3.2 core profile, where the issue will not be present (but you need to update your code a bit, including the shaders).



来源:https://stackoverflow.com/questions/28156524/meaning-of-index-parameter-in-glenablevertexattribarray-and-possibly-a-bug-i

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!