5
\$\begingroup\$

Here is my OpenGL "Hello, Triangles" program. I tried to avoid all the deprecated functions and put the triangles in a buffer. Even in two different ways.

It is a bit bloated by the shader compile status checking. But this error info is very helpful. It is why I mention the original author whose code got me started.

Maybe the #version 460 in the shaders should be reduced? First I also had compatibility (to access gl_Vertex built-in easily), but then I changed to a real in variable.

There are some subtle differences between the ...pointer20 and ...format46 version I don't really understand.

The official wiki on khronos.org also compares these two methods. In one place they mention glVertexAttribBinding(index, index). Yes, but which index? I tried to optically sort these out with BUF, VB and VAA.

Is this non-deprecated and "modern" OpenGL?

And what do you think of it? Thank you for your interest.

/* OpenGL Hello Triangles: minimal program with shaders and data buffers.
   Draws two overlapping (piercing, with depth) triangles. 
   No _deprecated_ functions.
/* Based on original by Jan Wedekind (shader integration) " */
/* Libs: -lGL -lGLEW -lglut */

#include <stdio.h>
#include <GL/glew.h>
#include <GL/glut.h>

/* GL objects/binds. 'VAA' must match the vertex shader's 'in' location (default 0)  */
/* 'VB' is 'vertex buffer binding point index'; 'VAA' is 'vertex sttribute array' */ 
int BUF;
const int VB = 3, VAA = 0;

int width = 450, height = 300;

/* Two triangles already in final clip space coords */
/* 'xyz' are the middle three; the '6' will be skipped by ...Format() */
/* 'w' (after xyz) is used for color */
float vertices[] = {
   6,  1, 1, 0.2, 0,
   6, -1,-1, 0.6, 0.1,
   6, -1, 1, 0.5, 0.3,

   6, 0.3, -.8, .9, 1,
   6, 0,   0.9, .1, .9,    /* z=0.1 --> near tip of second triangle */
   6, -.5, -.8, .9, .8,
};
/* Same two, but the extra '6' is after the actual vertex data. This way ...Pointer() can also use this array (version II)*/
float vertices_2[] = {
    1, 1, 0.2, 0,    6,
   -1,-1, 0.6, 0.1,  6,
   -1, 1, 0.5, 0.3,  6,

   0.3,  -.8, .9,  1,   6, 
   0,    0.9, 0.1, .9,  6,
   -.5,  -.8, .9,  .8,  6,
};

/* Shaders as strings. Ugly but practical */
/* Vertex shader. Invents some color from the 'w' column 
   xyz position is just passed on (no perspective) */
const char *vertex_src = "#version 460\n"

"   in  vec4 Vertex;                            \n\
    out vec4 Vcolor;                            \n\
    void main() {                                     \n\
        gl_Position = vec4(Vertex.xyz, 1);            \n\
        Vcolor = vec4(Vertex.y, .4, Vertex.w, 0.5);   \n\
    }                                                 \n\
";

/* Fragment shader. Applies the color (interpolated) */
const char *fragment_src = "#version 460\n"

"   in vec4 Vcolor;                             \n\
    void main() {                               \n\
        gl_FragColor = Vcolor;                  \n\
    }                                           \n\
";

/* Shader compile/link status feedback */
char buffer[1024];
int param;
void
shader_status(int shader) {
    glGetShaderiv(shader, GL_COMPILE_STATUS, &param);
    if (param)
        return;
    glGetShaderInfoLog(shader, 1024, NULL, buffer);
    fprintf(stderr, "Shader compile status:\n%s\n", buffer);
}
void
program_status(int prog) {
    glGetProgramiv(prog, GL_LINK_STATUS, &param);
    if (param)
        return;
    glGetProgramInfoLog(prog, 1024, NULL, buffer);
    fprintf(stderr, "Program link status:\n%s\n",  buffer);
}

/* Shader object from string; compile and check */
int 
make_shader(const char *src, int sh_typ) {

    int shader = glCreateShader(sh_typ);
    glShaderSource(shader, 1, &src, NULL);
    glCompileShader(shader);
    shader_status(shader);
    return shader;  
}
/* Program object from a vertex and a fragment shader; link and check */
int
make_program(int vert, int frag) {

    int prog = glCreateProgram();
    glAttachShader(prog, vert);
    glAttachShader(prog, frag);
    glLinkProgram(prog);
    program_status(prog);
    return prog;
}  

/* Prepare Data 2nd version: Traditional with BindBuffer(), VertexAttribPointer() */
void
init_bufs_pointer20(void) {

    glGenBuffers(1, &BUF);
    glBindBuffer(GL_ARRAY_BUFFER, BUF);

    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_2), vertices_2, GL_STATIC_DRAW);

    glVertexAttribPointer(VAA, 4, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0);       //..., stride, pointer)
    glEnableVertexAttribArray(VAA);
}

/* Prepare data: New with BindVertexBuffer(), VertexAttribFormat(), NamedBufferData() */
/* VertexAttribBinding() is done outside (= final step) */
void
init_bufs_format46(void) {

    glCreateBuffers(1, &BUF);
    glNamedBufferData(BUF, sizeof vertices, vertices, GL_STATIC_DRAW);

    glBindVertexBuffer(VB, BUF, 0, 5*sizeof(float));                               //..., offset, stride)

    glVertexAttribFormat(VAA, 4, GL_FLOAT, GL_FALSE, 1*sizeof(float));             // ..., reloffset)  -> skip first float 
    glEnableVertexAttribArray(VAA);
}

/* glut callbacks */
void 
display(void) {

    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    glDrawArrays(GL_TRIANGLES, 0, 2*3);
    glutSwapBuffers();
}
void 
reshape(int width, int height) {

    glViewport(0, 0, width, height);
}

int
main(int argc, char** argv) {

    /* Utils for GL */
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(width, height);
    glutCreateWindow("mini");

    glutDisplayFunc(display);
    glutReshapeFunc(reshape);

    glewInit();
    
    /* Install shaders */
    int VERT_SH = make_shader(vertex_src,   GL_VERTEX_SHADER);
    int FRAG_SH = make_shader(fragment_src, GL_FRAGMENT_SHADER);

    int PROG = make_program(VERT_SH, FRAG_SH);
    glUseProgram(PROG);
    
    /* Define triangle data ("vertex array organization")*/  
    init_bufs_format46();
    /* Activate attribs-buffer combo for drawing */
    glVertexAttribBinding(VAA, VB);

    /* Alternate data method */
    //init_bufs_pointer20();  
    
    glClearColor(0,0,0,0);
    
    /* Blend _or_ Cover ? And if yes, how ?*/
    //glEnable(GL_BLEND);
    //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    //glBlendEquation(GL_FUNC_SUBTRACT);

    glEnable(GL_DEPTH_TEST);
    /* invert depth */
    //glDepthFunc(GL_GREATER); 
    //glClearDepth(0);

    glutMainLoop();

    return 0;
}

And this is how it looks. Minimal color and minimal 3D. Much more fancy than a single unicolored triangle.

With Depth

\$\endgroup\$
1
\$\begingroup\$

If you don't want obsolete and deprecated stuff, don't use GLUT, see Is GLUT obsolete?. Use GLFW instead. I've personally used it and it is in my opinion easy to learn and much better than GLUT.

Accoding to the poster, they are using FreeGLUT and not GLUT (I don't think you can tell from their code). Even so, I still recommend GLFW over FreeGLUT because it handles Unicode input and HiDPI displays better and it allows for more finegrained control over the main loop. There is also much more activity in the GLFW project than in the FreeGLUT project, suggesting that the former has the larger developer mindshare.

Always compile with -Wall -Werror. In you code there are some obvious type errors you need to take care of.

There is not enough error-checking in many of your functions. For example in:

int
make_shader(const char *src, int sh_typ) {

    int shader = glCreateShader(sh_typ);
    glShaderSource(shader, 1, &src, NULL);
    glCompileShader(shader);
    shader_status(shader);
    return shader;
    }

you are not checking the return value of glCreateShader. This could cause weird and hard to debug errors in other parts of your code. I suggest something like this:

// Returns the shader id or 0 if shader creation failed.
GLuint
make_shader(const GLchar *src, GLenum shader_type) {

    GLuint shader = glCreateShader(shader_type);
    if (!shader) {
        return shader;
    }

    glShaderSource(shader, 1, &src, NULL);
    glCompileShader(shader);

    GLint compile_ok = GL_FALSE;
    GLint log_length;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_ok);
    if (!compile_ok) {
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
        GLchar *log = (GLchar *)malloc(sizeof(GLchar) * log_length);
        glGetShaderInfoLog(shader, log_length, NULL, log);
        fprintf(stderr, "Shader compilation failed:\n%s\n", log);
        free(log);
        return 0;
    }
    shader_status(shader);
    return shader;
}

And roughly the same for glCreateProgram/glLinkProgram. You get the idea. Note that I've changed the code so that it uses the proper GL types. You should also check the return value of make_shader rather than assuming that it will always succeed.

I don't know what the difference between "pointer20" and "format46" are. Pointer20 is the way I've written GL code, but apparently both methods work. However, I'd caution against storing colors in the same array as the vertices. It's much better to have one array for triangle vertices, one for colors, one for normals, one for texture coordinates, etc.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Original GLUT, but not freeglut is obsolte. GLFW seems a good (or better?) alternative to freeglut. <GL/glut.h> is freeglut's header. \$\endgroup\$
    – user239275
    Mar 18 at 12:02
  • \$\begingroup\$ Shader compile errors: I just continue, true, and sometimes get white distorted triangles (or a black screen). But the error log is there to show the reason. To me this is 90% OK; so yes, some room for more error checking... \$\endgroup\$
    – user239275
    Mar 18 at 12:15
  • \$\begingroup\$ One advantage of the newer (OpenGL 4.3) VertexAttribFormat() seems to be this Array-of-struct layout (better locality) of vertex attributes. Maybe my 5-attribute array demonstrates how the new command can freely choose the 1-4 columns (with "reloffset"). Of course, the way I squeezed one color component into the 4. column after xyz is just a hack, to be able to see two triangles and not just a silhouette. Looks like there is a design decision to be made. \$\endgroup\$
    – user239275
    Mar 18 at 12:47
  • \$\begingroup\$ There is no conclusive performance advantage in interleaving the vertex buffer data. See Storing vertex data: To interleave or not to interleave? and Vertex Specification Best Practices. The main disadvantage of interleaving is that it wastes memory and slows down operations that only requires some of the vertex attributes. \$\endgroup\$ Mar 18 at 22:05
0
\$\begingroup\$

The difference between VertexAtrribPointer() and ...Format is not as big; if you give the the pointer argument correctly, it works:

glVertexAttribPointer(VAA, 4, GL_FLOAT, GL_FALSE, 
      5*sizeof(float), (void*)(1*sizeof(float)));       //..., stride, pointer)

There is (was?) a glVertexPointer(), but that is missing in the current gl4 refpages. I found it in 'perf/glslstatechange.c' (mesa-demos). It uses no Buffer commands (!), but obviously has to do some other tricks. The goal is to call UseProgram() all the time. The (void*) cast has historical reasons. In this demo, that argument is simple the array name - no cast.


So for data selection ...Pointer and ...Format offer the same two arguments (5 and 1 in OP).

The main difference is the extra layer the newer glVertexAttribFormat needs (or allows)

glBindVertexBuffer(VB, BUF, 0, 5*sizeof(float));
...
glVertexAttribBinding(VAA, VB)

For the price of two additional commands, you get an extra layer and don't have to write (void*).

This extra layer VB should help when you update parts of the vertex attributes. Actually, glVertexAttribBinding(VAA, VB) is not the part of the price but part of the purchase.

\$\endgroup\$
0
\$\begingroup\$

The key to understand these historical changes in OpenGL is the "ARB Extensions Specifications". They run from #1 in 2002 to #190 in 2019; the ones I checked mostly have a useful "Overview" intro, explaining why this ARB spec was needed.

The OpenGL wiki (khronos.org) sometimes copies parts from the ARBs. The ARBs also update the specs (new functions) - no wonder the specs are so hard to read after all these additions.

ARB direct_state_acces reduces the bind-to-edit actions; it defines many new calls.

glVertexAttribFormat is from ARB vertex_attrib_binding.

Here the function in question. I put the official function description in comments. The vertex data this time is split into a position and a color part - vertically interlaced.

void
init_bufs_format46(void) {

/* ARB#164 direct_state_access, 2014 
 - New buffer objects, initialized with unspecified target 
 - New Data Store for (named) Buffer Object */
glCreateBuffers(1, BUF);
glNamedBufferData(BUF[0], sizeof vertices, vertices, GL_STATIC_DRAW);


/* ARB#125 vertex_attrib_binding, 2012 
 - Bind a vertex buffer bind point (0-15) to a buffer  
 - Specify layout of a generic vertex attribute array 
 - Associate vertex attribute and vertex buffer binding [point] */
VB[1] = 1;
glBindVertexBuffer        (VB[1], BUF[0], 0,          3*sizeof(float));       //..., offset, stride)
glVertexAttribFormat      (GVAA0, 3,      GL_FLOAT, 0, 0);
glVertexAttribBinding     (GVAA0, VB[1]);

glEnableVertexAttribArray (GVAA0);

/* GVAA "1" is color here; same buffer but with an offset and a different stride */
VB[2] = 2;
int buf_offset = 6*3*sizeof(float);
glBindVertexBuffer        (VB[2], BUF[0], buf_offset, 4*sizeof(float));
glVertexAttribFormat      (1,     3,      GL_FLOAT, 0, 0); 
glVertexAttribBinding     (1,     VB[2]);

glEnableVertexAttribArray (1);
}

The VB(BP)s just get integers from 0 to 15. No Gen- or Create-someobj. I left the "1" in the 2nd block naked; it would be GVAA1, or then GVAA_color in this case.

(The vertex shader now has a second in, and now it just passes on both unchanged.)

float vertices[] = {
   1, 1, 0.2,
   -1,-1, 0.6,
   -1, 1, 0.5,
   //-.1,.1,.5,

   0.3, -.8, .9,
   0,   0.9, .1,    /* z=0.1 --> near tip of second triangle */
   -.5, -.8, .8,
//};
//float colors[] = {
    .8, .6, .1,.2,
    .6, .5, .1,.2,
    .9, .3, .1,.2,

    .1, .9, .6,.1,
    .7, .6, .7,.1,
    .3, .7, .9,.1,
};

ARB #28 vertex_buffer_object (2003) is also interesting:

What should this extension be called?

    RESOLVED: By unanimous consent among the working group members,
    the name was chosen to be "ARB_vertex_buffer_object".  A large
    number of other names were considered throughout the lifetime of
    the proposal, especially "vertex_array_object" (originally),
    "buffer_object" (later on), and "memory_object" (near the end),
    but the name "vertex_buffer_object" was ultimately chosen.

And vertex_array_object became #54. (A container object).


They probably think: with a front cover like that on the specification, explanations are not necessary.

enter image description here

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy