c++linuxopenglglx

Drawing triangles with opengl with GL/gl.h and GL/glx.h on x11 System


I have read and pieced multiple projects together in order to create an x11 window with open gl working, with the preinstalled GL/gl.h and GL/glx.h. The problem I get is that the triangles I want to draw to the screen does not show. What I think is that I have not setup any projecting parameters etc, or that the triangles doesn't get drawn to the space I want to draw to.

I do get a window and I am able to setup xevents that triggers after I have subscribed with eventmasks. Pressing 'esc' key will trigger an event which will in the end call Shutdown() and break the loop, free up x11 and gl stuff, and lastly exit the program. So the only thing that doesn't work is the drawing to screen stuff which basically is the main point of my program.

How can I resolve this?

main.cpp:

#include <cstring>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysymdef.h>

#include <GL/gl.h>
#include <GL/glx.h>

#include <sys/time.h>
#include <unistd.h>

#define WINDOW_WIDTH    800
#define WINDOW_HEIGHT   600
#define FPS 30
#define TEST_LOCAL

int shutdown = 0;

extern bool Initialize(int w, int h);
extern bool Update(float deltaTime);
extern void Render();
extern void HandleKeyboardEvents( XEvent );
extern void Resize(int w, int h);
extern void Shutdown();

typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*);

#define SKIP_TICKS      (1000 / FPS)

static double GetMilliseconds() {
    static timeval s_tTimeVal;
    gettimeofday(&s_tTimeVal, NULL);
    double time = s_tTimeVal.tv_sec * 1000.0; // sec to ms
    time += s_tTimeVal.tv_usec / 1000.0; // us to ms
    return time;
}

static bool isExtensionSupported(const char *extList, const char *extension) {
    return strstr(extList, extension) != 0;
}

int main(int argc, char** argv) {
    Display* display;
    Window window;
    Screen* screen;
    int screenId;
    XEvent ev;

    // Open the display
    display = XOpenDisplay(NULL);
    if (display == NULL) {
        std::cout << "Could not open display\n";
        return 1;
    }
    screen = DefaultScreenOfDisplay(display);
    screenId = DefaultScreen(display);
    
    // Check GLX version
    GLint majorGLX, minorGLX = 0;
    glXQueryVersion(display, &majorGLX, &minorGLX);
    if (majorGLX <= 1 && minorGLX < 2) {
        std::cout << "GLX 1.2 or greater is required.\n";
        XCloseDisplay(display);
        return 1;
    }

    GLint glxAttribs[] = {
        GLX_X_RENDERABLE    , True,
        GLX_DRAWABLE_TYPE   , GLX_WINDOW_BIT,
        GLX_RENDER_TYPE     , GLX_RGBA_BIT,
        GLX_X_VISUAL_TYPE   , GLX_TRUE_COLOR,
        GLX_RED_SIZE        , 8,
        GLX_GREEN_SIZE      , 8,
        GLX_BLUE_SIZE       , 8,
        GLX_ALPHA_SIZE      , 8,
        GLX_DEPTH_SIZE      , 24,
        GLX_STENCIL_SIZE    , 8,
        GLX_DOUBLEBUFFER    , True,
        None
    };
    
    int fbcount;
    GLXFBConfig* fbc = glXChooseFBConfig(display, screenId, glxAttribs, &fbcount);
    if (fbc == 0) {
        std::cout << "Failed to retrieve framebuffer.\n";
        XCloseDisplay(display);
        return 1;
    }

    // Pick the FB config/visual with the most samples per pixel
    int best_fbc = -1, worst_fbc = -1, best_num_samp = -1, worst_num_samp = 999;
    for (int i = 0; i < fbcount; ++i) {
        XVisualInfo *vi = glXGetVisualFromFBConfig( display, fbc[i] );
        if ( vi != 0) {
            int samp_buf, samples;
            glXGetFBConfigAttrib( display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf );
            glXGetFBConfigAttrib( display, fbc[i], GLX_SAMPLES       , &samples  );

            if ( best_fbc < 0 || (samp_buf && samples > best_num_samp) ) {
                best_fbc = i;
                best_num_samp = samples;
            }
            if ( worst_fbc < 0 || !samp_buf || samples < worst_num_samp )
                worst_fbc = i;
            worst_num_samp = samples;
        }
        XFree( vi );
    }
    GLXFBConfig bestFbc = fbc[ best_fbc ];
    XFree( fbc ); // Make sure to free this!

    
    XVisualInfo* visual = glXGetVisualFromFBConfig( display, bestFbc );
    if (visual == 0) {
        std::cout << "Could not create correct visual window.\n";
        XCloseDisplay(display);
        return 1;
    }
    
    if (screenId != visual->screen) {
        std::cout << "screenId(" << screenId << ") does not match visual->screen(" << visual->screen << ").\n";
        XCloseDisplay(display);
        return 1;

    }

    // Open the window
    XSetWindowAttributes windowAttribs;
    windowAttribs.border_pixel = BlackPixel(display, screenId);
    windowAttribs.background_pixel = WhitePixel(display, screenId);
    windowAttribs.override_redirect = True;
    windowAttribs.colormap = XCreateColormap(display, RootWindow(display, screenId), visual->visual, AllocNone);
    windowAttribs.event_mask = ExposureMask;
    window = XCreateWindow(display, RootWindow(display, screenId), 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 0, visual->depth, InputOutput, visual->visual, CWBackPixel | CWColormap | CWBorderPixel | CWEventMask, &windowAttribs);

    // Redirect Close
    Atom atomWmDeleteWindow = XInternAtom(display, "WM_DELETE_WINDOW", False);
    XSetWMProtocols(display, window, &atomWmDeleteWindow, 1);

    // Create GLX OpenGL context
    glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0;
    glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc) glXGetProcAddressARB( (const GLubyte *) "glXCreateContextAttribsARB" );
    
    int context_attribs[] = {
        GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
        GLX_CONTEXT_MINOR_VERSION_ARB, 2,
        GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
        None
    };

    GLXContext context = 0;
    const char *glxExts = glXQueryExtensionsString( display,  screenId );
    if (!isExtensionSupported( glxExts, "GLX_ARB_create_context")) {
        std::cout << "GLX_ARB_create_context not supported\n";
        context = glXCreateNewContext( display, bestFbc, GLX_RGBA_TYPE, 0, True );
    }
    else {
        context = glXCreateContextAttribsARB( display, bestFbc, 0, true, context_attribs );
    }
    XSync( display, False );

    // Verifying that context is a direct context
    if (!glXIsDirect (display, context)) {
        std::cout << "Indirect GLX rendering context obtained\n";
    }
    else {
        std::cout << "Direct GLX rendering context obtained\n";
    }
    glXMakeCurrent(display, window, context);

    std::cout << "GL Renderer: " << glGetString(GL_RENDERER) << "\n";
    std::cout << "GL Version: " << glGetString(GL_VERSION) << "\n";
    std::cout << "GLSL Version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << "\n";

    if (!Initialize(WINDOW_WIDTH, WINDOW_HEIGHT)) {
        glXDestroyContext(display, context);
        XFree(visual);
        XFreeColormap(display, windowAttribs.colormap);
        XDestroyWindow(display, window);
        XCloseDisplay(display);
        return 1;
    }

    glMatrixMode( GL_PROJECTION );
    glOrtho( 0, 640, 480, 0, -1, 1 );

    XSelectInput( display, window, PointerMotionMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask );

    // Show the window
    XClearWindow(display, window);
    XMapRaised(display, window);

    double prevTime = GetMilliseconds();
    double currentTime = GetMilliseconds();
    double deltaTime = 0.0;

    timeval time;
    long sleepTime = 0;
    gettimeofday(&time, NULL);
    long nextGameTick = (time.tv_sec * 1000) + (time.tv_usec / 1000);

    // Enter message loop
    while (shutdown != 1) {
        if (XPending(display) > 0) {
            XNextEvent(display, &ev);
            if (ev.type == Expose) {
                XWindowAttributes attribs;
                XGetWindowAttributes(display, window, &attribs);
                Resize(attribs.width, attribs.height);
            }
            if (ev.type == ClientMessage) {
                if (ev.xclient.data.l[0] == atomWmDeleteWindow) {
                    break;
                }
            }
            else if (ev.type == DestroyNotify) { 
                break;
            }
        }

        currentTime = GetMilliseconds();
        deltaTime = double(currentTime - prevTime) * 0.001;
        prevTime = currentTime;

        if (!Update((float)deltaTime)) {
            break;
        }
        HandleKeyboardEvents( ev );
        Render();

        // Present frame
        glXSwapBuffers(display, window);

        // Limit Framerate
        gettimeofday(&time, NULL);
        nextGameTick += SKIP_TICKS;
        sleepTime = nextGameTick - ((time.tv_sec * 1000) + (time.tv_usec / 1000));
        usleep((unsigned int)(sleepTime / 1000));
    }

    std::cout << "Shutting Down\n";
    Shutdown();

    // Cleanup GLX
    glXDestroyContext(display, context);

    // Cleanup X11
    XFree(visual);
    XFreeColormap(display, windowAttribs.colormap);
    XDestroyWindow(display, window);
    XCloseDisplay(display);
    return 0;
}

#ifdef TEST_LOCAL
bool Initialize(int w, int h) {
    glClearColor(0.5f, 0.6f, 0.7f, 1.0f);
    glViewport(0, 0, w, h);
    return true;
}

bool Update(float deltaTime) {
    return true;
}

void HandleKeyboardEvents( XEvent ev ) {
    int x, y;

    switch ( ev.type ) {
        case ButtonPress:
            if ( ev.xbutton.button == 1 ) {
                std::cout << "Left mouse down \n";
            }
            break;
        case ButtonRelease:
            if ( ev.xbutton.button == 1 ) {
                std::cout << "Left mouse up \n";
            }
            break;
        case KeyPress:
            if ( ev.xkey.keycode == 9 ) { // ESC
                Shutdown();
            }
            break;
        case MotionNotify:
            x = ev.xmotion.x;
            y = ev.xmotion.y;
            //std::cout << "Mouse X:" << x << ", Y: " << y << "\n";
            break;  
    }
}

void Render() {
    glClear(GL_COLOR_BUFFER_BIT);

    glBegin(GL_TRIANGLES);
        glColor3f(  1.0f,  0.0f, 0.0f);
        glVertex3f( 0.0f, -1.0f, 0.0f);
        glColor3f(  1.0f,  0.0f, 0.0f);
        glVertex3f(-1.0f,  1.0f, 0.0f);
        glColor3f(  1.0f,  0.0f, 0.0f);
        glVertex3f( 1.0f,  1.0f, 0.0f);
    
    glEnd();
    glFlush();
}

void Resize(int w, int h) {
    glViewport(0, 0, w, h);
}

void Shutdown() {
    shutdown = 1;   
}
#endif

I compile with:

g++ -g -Wall -o _build/main main.cpp -I/opt/x11/include  -L/usr/x11/lib -lGL -lX11

OS:

Linux kali 5.9.0-kali4-amd64 #1 SMP Debian 5.9.11-1kali1 (2020-12-01) x86_64 GNU/Linux

Solution

  • Your code will not render the triangle, but will generate GL_INVALID_OPERATION on your glBegin/glEnd construct instead. The reason lies here:

    int context_attribs[] = {
        GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
        GLX_CONTEXT_MINOR_VERSION_ARB, 2,
        GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
        None
    };
    

    According to the GLX_ARB_create_context GLX extension spec (emphasis mine):

    If version 3.2 or greater is requested, the context returned may implement any of the following versions:

    • The requested profile of the requested version.
    • The requested profile of any later version, so long as no features have been removed from that later version and profile.

    with the profile being requested via the GLX_CONTEXT_PROFILE_MASK_ARB attribute, which you ommitted, so the default will apply:

    The default value for GLX_CONTEXT_PROFILE_MASK_ARB is GLX_CONTEXT_CORE_PROFILE_BIT_ARB. All OpenGL 3.2 implementations are required to implement the core profile, but implementation of the compatibility profile is optional.

    The legacy features like glBegin/glEnd drawing mode, the fixed-function pipeline, the matrix stack are all removed from core profiles of OpenGL.

    To get this code to run, you have 3 options:

    1. Request a GL version <= 2.1. This will get you the most compatibility with legacy implementations. And it will basically yield the same result as if you don't use the GLX_ARB_create_context extension at all.
    2. Explicitly request GL 3.2 with GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB (and remove the GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB). This might or might not work depending on wether your implementation supports.
    3. Keep the GL context as 3.2 core, and just write core-profile compatible code.

    Option 3 is actually the only one which makes sense in the year 2021. However, it will require much more setup code than you currently have. You will have to write your own shaders, and set up a VAO together with a VBO for the vertex attributes and the respective data arrays.

    Note that option 3 will also break the

    with the preinstalled GL/gl.h and GL/glx.h.

    part of your premise. You'll want to drop at least the gl.h header in favor of one generated by a GL loader.