openglx11glx

glXCreateContext Magically Maps Window


After creating a display, I make a window with XCreateWindow. Then, as described here, I call XMapWindow and then immediately XUnmapWindow; this lets the X server know about the window so that commands (e.g. XMoveWindow) don't silently fail.

The window is invisible at this point, as it should be. I can stop execution with e.g. getchar. Definitely invisible.

Then I call glXCreateContext, and the window appears, just as if I had called XMapWindow again! Sorcery! I have stopped execution immediately before and immediately after, so I know it's glXCreateContext.


This makes no sense. I skimmed the documentation, but there's really no way this could possibly happen. Any guesses?


EDIT: Here's a simple example:

//Compile with "g++ <filename>.cpp -std=c++11 -lX11 -lGL"

#include <cassert>
#include <cstdio>

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

#include <GL/glx.h>


static Display* display;


static void _x11_map_window(Window window) {
    printf("Mapping window %lu; RETURN to continue\n",window); getchar();
    XMapWindow(display, window);
    printf("Mapped window!  RETURN to continue\n"); getchar();
}
static void _x11_unmap_window(Window window) {
    printf("Unmapping window %lu; RETURN to continue\n",window); getchar();
    XUnmapWindow(display, window);
    printf("Unmapped window!  RETURN to continue\n"); getchar();
}


int main(int argc, char* argv[]) {
    /* ##### MAKE DISPLAY ##### */
    display = XOpenDisplay(nullptr);


    /* ##### MAKE VISUAL INFO. ##### */
    int attributes[] = { //can't be const b/c X11 doesn't like it.  Not sure if that's intentional or just stupid.
        GLX_RGBA, //apparently nothing comes after this?
        GLX_RED_SIZE,    8,
        GLX_GREEN_SIZE,  8,
        GLX_BLUE_SIZE,   8,
        GLX_ALPHA_SIZE,  8,
        //Ideally, the size would be 32 (or at least 24), but I have actually seen
        //  this size (on a modern OS even).
        GLX_DEPTH_SIZE, 16,
        GLX_DOUBLEBUFFER, True,
        None
    };

    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wold-style-cast" //Because of X11's cruft in "DefaultScreen".
    XVisualInfo* visual_info = glXChooseVisual(display, DefaultScreen(display), attributes);
    #pragma GCC diagnostic pop
    assert(visual_info!=nullptr);


    /* ##### MAKE WINDOW ##### */
    Window parent = XDefaultRootWindow(display);

    Colormap colormap = XCreateColormap(display, parent, visual_info->visual, AllocNone);

    XSetWindowAttributes window_attributes_set;
    window_attributes_set.colormap = colormap;
    window_attributes_set.background_pixel = 0; //This and next b/c of http://stackoverflow.com/questions/3645632/how-to-create-a-window-with-a-bit-depth-of-32
    window_attributes_set.border_pixel = 0;     //especially resulting in BadMatch error on Raspberry Pi.  Also changes bit fields below in XCreateWindow.
    window_attributes_set.event_mask = ExposureMask | KeyPressMask;

    int position[2]={50,50}, dimensions[2]={128,128};
    Window window = XCreateWindow(
        display, parent,
        position[0],position[1], static_cast<unsigned int>(dimensions[0]),static_cast<unsigned int>(dimensions[1]), //Note: the documentation must be wrong; this thing wants unsigned ints.
        0u,
        visual_info->depth,
        InputOutput,
        visual_info->visual,
        //CWColormap|CWEventMask,
        CWBackPixel|CWColormap|CWBorderPixel | CWEventMask,
        &window_attributes_set
    );
    assert(window!=0);
    printf("Created window %lu\n",window);
    XStoreName(display, window, "[default title]");
    XSelectInput(display, window,
        //http://www.tronche.com/gui/x/xlib/events/mask.html#NoEventMask
        //http://www.tronche.com/gui/x/xlib/events/processing-overview.html
        ExposureMask |
        KeyPressMask | KeyReleaseMask |
        ButtonPressMask | ButtonReleaseMask | //ButtonMotionMask |
        //EnterWindowMask | LeaveWindowMask |
        PointerMotionMask |
        //KeymapStateMask | FocusChangeMask | ColormapChangeMask |
        StructureNotifyMask //Resizing, etc.
        //PropertyChangeMask
    );

    Atom wm_delete = XInternAtom(display, "WM_DELETE_WINDOW", True);
    XSetWMProtocols(display, window, &wm_delete, 1);

    XMoveWindow(display, window, 100,100);

    //As described here: http://stackoverflow.com/questions/14801536/xmovewindow-not-working-before-xmapwindow
    //  "the X server doesn't have to know about a window before it is mapped for the first time".  Hence,
    //  map the window and then unmap it so that the X server knows about it.  This is important because some
    //  functions silently fail (e.g. XMoveWindow) when the X server is oblivious.
    _x11_map_window(window);
    _x11_unmap_window(window);


    /* ##### MAKE RENDER CONTEXT ##### */
    GLXContext render_context = glXCreateContext(display, visual_info, nullptr, True);
    assert(render_context!=nullptr);


    /* ##### MORE STUFF WOULD GO HERE ##### */
    while (1);

    return 0;
}

Also demonstrates the failure of either XCreateWindow or XMoveWindow to set the window position before map/unmap.


Solution

  • Investigating this has been difficult, but I've resolved it and the TL;DR is:

    1. This isn't actually about glXCreateContext(...). This is about an apparent timing bug in certain implementations of X.
    2. This issue was exposed by a workaround I had written on the basis of misinformation. The thing I wanted to do should instead be accomplished by means of a different workaround.

    Description of Underlying Issues

    When a window is created, the window manager wraps it in a new window, as long as the override redirect isn't set (attributes .override_redirect and flag CWOverrideRedirect on window creation). This is so that it can do things like add a frame and buttons.

    Unfortunately, the window manager can (and does, at least before the window is mapped) use this as an excuse to ignore behavior such as XMoveWindow(...). This has led to the misconception that one should map and then unmap the window so that the X server "knows about it".

    This exposes the apparent bug. On the system in question (stock Ubuntu in VirtualBox), mapping and then unmapping the window immediately after causes the window to remain mapped.

    I tried many things, such as putting XFlush(...) or XSync(...) calls around the map/unmap calls (which also allowed me to show that glXCreateContext(...) is not at issue). However, what finally got it to work as-expected was to add sleeps. A delay of 0.1 seconds made the window appear and disappear. A delay of 0.01 seconds made the window remain mapped. This was rather frustrating to figure out (I had the aforementioned getchar()s and printf(...)s, and this introduced enough latency while debugging that the problem couldn't be reproduced).

    The following (probably non-minimal) code works as-written, but removing the nanosleep(...) calls will cause the problem:

    struct timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = 200000000;
    
    XFlush(_display);
    nanosleep(&ts, nullptr);
    XMapWindow(display, window);
    XFlush(display);
    nanosleep(&ts, nullptr);
    XUnmapWindow(display, window);
    XFlush(_display);
    

    Presumably the delay allows catching up to the map/unmap events or something. I'm not sure this is a full-fledged bug, but it's certainly a usability defect. (If this gives you enough information to explain what's going on here, feel free to edit this answer.)

    However, as-mentioned, this workaround is based on a misconception! The X server already knows about the new window. It's just flagrantly ignoring you. To fix this, we can hint to the windowing system that it shouldn't be so rude. Since this doesn't rely on map/unmap, the erroneous behavior doesn't happen anymore.