Home > Software design >  Is there a way to create GLX context after Xlib's window creation?
Is there a way to create GLX context after Xlib's window creation?

Time:10-26

I'm trying to create OpenGLx context after the Xlib's window creation. I'm trying to separate the Xlib window creation and opengl context creation into two different phases. Win32 window-opengl context creation was rather simple but I couldnt find any resource that illustrates the same process with Xlib-opengl in linux

This is how its done for xlib-linux

GLint glxAttribs[] = {
    GLX_RGBA,
    GLX_DOUBLEBUFFER,
    GLX_DEPTH_SIZE,     24,
    GLX_STENCIL_SIZE,   8,
    GLX_RED_SIZE,       8,
    GLX_GREEN_SIZE,     8,
    GLX_BLUE_SIZE,      8,
    GLX_SAMPLE_BUFFERS, 0,
    GLX_SAMPLES,        0,
    None
};


XVisualInfo* visual = glXChooseVisual(display, screenId, glxAttribs);
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, 320, 200, 0, visual->depth, InputOutput, visual->visual, CWBackPixel | CWColormap | CWBorderPixel | CWEventMask, &windowAttribs);

This is how its done in windows

const WindowsWindow* pWin32Window = (const WindowsWindow*)pOwnerWindow;

        HWND windowHandle = pWin32Window->GetWin32WindowHandle();
        HDC windowDeviceContext = pWin32Window->GetWin32WindowDeviceContext();

        /*
        * Create pixel format
        */
        PIXELFORMATDESCRIPTOR pfd = { sizeof(pfd),1 };
        memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
        pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
        pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
        pfd.iPixelType = PFD_TYPE_RGBA;
        pfd.nVersion = 1;
        pfd.cColorBits = OpenGLDeviceUtilsWin32::GetColorBits(desc.SwapchainBufferFormat);
        pfd.cAlphaBits = OpenGLDeviceUtilsWin32::GetAlphaBits(desc.SwapchainBufferFormat);
        pfd.cDepthBits = OpenGLDeviceUtilsWin32::GetDepthBits(desc.SwapchainDepthStencilBufferFormat);
        pfd.cStencilBits = OpenGLDeviceUtilsWin32::GetStencilBits(desc.SwapchainDepthStencilBufferFormat);
        pfd.cAuxBuffers = 3;
        pfd.iLayerType = PFD_MAIN_PLANE;

        const int pixelFormatIndex = ChoosePixelFormat(windowDeviceContext, &pfd);
        ASSERT(pixelFormatIndex != 0,"OpenGLDevice","Invalid pixel format");

        ASSERT(SetPixelFormat(windowDeviceContext, pixelFormatIndex, &pfd), "OpenGLDevice", "Win32 window rejected the specified pixel format");

        HGLRC tempContext = wglCreateContext(windowDeviceContext);
        ASSERT(tempContext != NULL, "OpenGLDevice", "Creation of wgl dummy context failed!");

        wglMakeCurrent(windowDeviceContext, tempContext);

        PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL;
        wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
        ASSERT(wglCreateContextAttribsARB != NULL, "OpenGLDevice", "WGL get proc address failed!");

But I would expect something like this.

  • Create xlib window
  • Check for glx attribs if the window can support that pixel format
  • Create glx context using pixel format

But instead it goes as

  • Create window with your specific glx attribs
  • Create glx context

I wonder if there is a way for us to create window without letting xlib know we are going to use it for opengl and implement OpenGL specific setup for window creation process.

CodePudding user response:

Honestly, I do not understand your motivation.

Many implementations like GLFW gets Visual from GLX/EGL APIs (including glXChooseFBConfig) and use it when creating a window. The GLX/EGL stuff part can be abstracted by writing wrappers, so I don't see the need to go to the trouble of avoiding it.

That being said, it is still possible to avoid it, so I wrote the sample code for you.

// To build, execute the command below.
// c   -Wall -Wextra -std=c  17 -o main main.cpp -lX11 -lGLX -lGL

#include <cstdio>

#include <chrono>
#include <thread>

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

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <GL/glx.h>
#include <GL/glxext.h>

#define OGL_MAJOR_VERSION 3
#define OGL_MINOR_VERSION 3

#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 360
#define FPS 60

static double get_time() {
    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;
}

struct TestWindowConfig {
    int width = 640;
    int height = 360;
};

class TestWindow final {
  public:
    explicit TestWindow(const TestWindowConfig& config) : m_config(config) {}
    virtual ~TestWindow() {
        if (m_display) {
            if (m_xid) {
                XDestroyWindow(m_display, m_xid);
            }
            XCloseDisplay(m_display);
        }
    }

    bool create() {
        m_display = XOpenDisplay(NULL);
        if (!m_display) {
            fprintf(stderr, "XOpenDisplay() failed\n");
            return false;
        }

        XSetWindowAttributes x_attr;
        x_attr.override_redirect = False;
        x_attr.border_pixel = 0;

        m_xid = XCreateWindow(m_display, DefaultRootWindow(m_display), 0, 0, m_config.width,
                              m_config.height, 0, CopyFromParent, InputOutput, CopyFromParent,
                              CWOverrideRedirect | CWBorderPixel, &x_attr);
        if (!m_xid) {
            fprintf(stderr, "XOpenDisplay() failed\n");
            return false;
        }
        XStoreName(m_display, m_xid, "X11-GLX Sample");
        XMapWindow(m_display, m_xid);

        m_wm_delete_window = XInternAtom(m_display, "WM_DELETE_WINDOW", True);
        XSetWMProtocols(m_display, m_xid, &m_wm_delete_window, 1);

        return true;
    }

    void show() const {
        if (m_display && m_xid) {
            XMapRaised(m_display, m_xid);
        }
    }

    bool poll_events() {
        if (!m_display) {
            fprintf(stderr, "Display is null\n");
            return false;
        }

        while (XPending(m_display) > 0) {
            XEvent ev;
            XNextEvent(m_display, &ev);
            if (ev.type == ClientMessage) {
                if ((Atom)ev.xclient.data.l[0] == m_wm_delete_window) {
                    m_should_close = true;
                }
            }
        }

        return true;
    }

    bool should_close() const { return m_should_close; }

    Display* display() const { return m_display; }

    Window xid() const { return m_xid; }

    int screen_id() const { return DefaultScreen(m_display); }

  private:
    TestWindowConfig m_config;
    Display* m_display = nullptr;
    Window m_xid = 0;
    Atom m_wm_delete_window;

    bool m_should_close = false;
};

class TestGLContext final {
  public:
    explicit TestGLContext() = default;

    virtual ~TestGLContext() = default;

    bool create(const TestWindow& window) {
        // clang-format off
        int visual_attr[] = {
            GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
            GLX_RENDER_TYPE,   GLX_RGBA_BIT,
            GLX_RED_SIZE,      8,
            GLX_GREEN_SIZE,    8,
            GLX_BLUE_SIZE,     8,
            GLX_ALPHA_SIZE,    8,
            GLX_DEPTH_SIZE,    0,
            GLX_STENCIL_SIZE,  0,
            GLX_DOUBLEBUFFER,  True,
            None
        };
        // clang-format on
        int cfg_count;
        auto fb_configs =
            glXChooseFBConfig(window.display(), window.screen_id(), visual_attr, &cfg_count);
        if (!fb_configs || (cfg_count < 1)) {
            fprintf(stderr, "glXChooseFBConfig(): No config found\n");
            return false;
        }
        PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB =
            (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddressARB(
                (const GLubyte*)"glXCreateContextAttribsARB");
        if (!glXCreateContextAttribsARB) {
            fprintf(stderr, "Failed to load glXCreateContextAttribsARB\n");
            return false;
        }
        // clang-format off
        int ctx_attr[] = {
            GLX_CONTEXT_PROFILE_MASK_ARB,  GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
            GLX_CONTEXT_MAJOR_VERSION_ARB, OGL_MAJOR_VERSION,
            GLX_CONTEXT_MINOR_VERSION_ARB, OGL_MINOR_VERSION,
            0, 0
        };
        // clang-format on
        m_ctx = glXCreateContextAttribsARB(window.display(), fb_configs[0], NULL, True, ctx_attr);
        if (!m_ctx) {
            fprintf(stderr, "Failed to create GLX Context\n");
            return false;
        }

        m_should_destroy = true;

        return true;
    }

    bool make_current(const TestWindow& window) {
        if (glXMakeCurrent(window.display(), window.xid(), m_ctx) != True) {
            fprintf(stderr, "glXMakeCurrent() Failed\n");
            return false;
        }
        return true;
    }

    void swap_buffers(const TestWindow& window) { glXSwapBuffers(window.display(), window.xid()); }

    static void* get_proc_address(const char* name) {
        return reinterpret_cast<void*>(glXGetProcAddress((const GLubyte*)name));
    }

    void destroy(const TestWindow& window) {
        glXDestroyContext(window.display(), m_ctx);
        m_should_destroy = false;
    }

    bool should_destroy() const { return m_should_destroy; }

  private:
    GLXContext m_ctx;

    bool m_should_destroy = false;
};

int main() {
    // 1. Prepare Window and OpenGL Context
    // In normal design, TestWindow should have its GLContext within itself.
    // But, in order to fit your needs, I separated these explicitly.

    TestWindowConfig config{.width = WINDOW_WIDTH, .height = WINDOW_HEIGHT};
    TestWindow window{config};
    TestGLContext glctx{};

    if (!window.create()) {
        return 1;
    }

    if (!glctx.create(window) || !glctx.make_current(window)) {
        if (glctx.should_destroy()) {
            glctx.destroy(window);
        }
        return 1;
    }

    // 2. Load OpenGL functions
    // In normal cases, you are always recommended to use loader libraries like glad.
    // In this example, I omited the loading part.
    //
    // if (!gladLoadGLLoader((GLADloadproc)glctx.get_proc_address)) {
    //     fprintf(stderr, "Failed to load OpenGL functions\n");
    //     return 1;
    // }

    // 3. Show the window and call OpenGL APIs
    // As above, there are various problems in this implentation for real use.
    window.show();

    double last_time = get_time();
    while (true) {
        if (!window.poll_events() || window.should_close()) {
            break;
        }

        auto delta_ms = get_time() - last_time;
        if (auto diff = (1000.0 / FPS) - delta_ms; diff > 0) {
            std::this_thread::sleep_for(std::chrono::milliseconds((long)diff));
            continue;
        }
        // fprintf(stderr, "delta: %f\n", delta_ms);

        glViewport(0, 0, config.width, config.height);
        glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        glctx.swap_buffers(window);

        last_time = get_time();
    }

    return 0;
}

CodePudding user response:

I'm trying to create OpenGLx context after the Xlib's window creation.

I don't really see your problem. On Win32 the usual stanza is:

  1. Create window
  2. Select pixelformat
  3. Set pixelformat on window
  4. Get HDC from window and use it to create context

On GLX the stanza is:

  1. Select visual for window
  2. Create window that's compatible with visual
  3. Create OpenGL context with the selected visual

Take note that in both Win32 and GLX there is no hard tie between the window and the OpenGL context. As long as the pixelformat/visual of a OpenGL context and a window are compatiple, you can use them with each other.

The only difference between GLX and Win32 is, how the pixelformat/visual is communicated to OpenGL context creation. In GLX it's done directy, in Win32 the pixelformat is communicated in a rather convoluted way by means of the HDC of a window. And take note that in order to obtain a modern OpenGL context you actually have to go the route of OpenGL context creation with attributes which works exactly the same in Win32 and GLX (with Win32 needing the added steps of creating a dummy OpenGL context first in order to obtain the function pointers to the wglCreateContextAttribsARB functions, which are directly available in GLX).

  • Related