3

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.

4

1 回答 1

1

对此进行调查很困难,但我已经解决了,TL;DR 是:

  1. 这实际上不是关于glXCreateContext(...). 这是关于 X 的某些实现中的一个明显的时序错误。
  2. 我根据错误信息编写的解决方法暴露了这个问题。我想做的事情应该通过不同的解决方法来完成。

潜在问题的描述

创建窗口时,窗口管理器会将其包装在新窗口中,只要未设置覆盖重定向(窗口创建时的属性.override_redirect和标志CWOverrideRedirect)。这样它就可以做一些事情,比如添加一个框架和按钮。

不幸的是,窗口管理器可以(并且确实,至少在窗口被映射之前)以此为借口忽略诸如XMoveWindow(...). 这导致了一种误解,即应该​​映射然后取消映射窗口,以便 X 服务器“知道它”。

这暴露了明显的错误。在有问题的系统上(VirtualBox 中的股票 Ubuntu),映射然后立即取消映射窗口会导致窗口保持映射状态。

我尝试了很多事情,例如在 map/unmap 调用周围放置XFlush(...)XSync(...)调用(这也让我可以证明这glXCreateContext(...)不是问题)。然而,最终让它按预期工作的是添加睡眠。0.1 秒的延迟使窗口出现和消失。0.01 秒的延迟使窗口保持映射。弄清楚这是相当令人沮丧的(我有前面提到getchar()的 s 和printf(...)s,这在调试时引入了足够的延迟,导致问题无法重现)。

以下(可能不是最小的)代码按原样工作,但删除nanosleep(...)调用会导致问题:

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);

大概延迟允许赶上地图/取消地图事件或其他东西。我不确定这是一个成熟的错误,但它肯定是一个可用性缺陷。(如果这为您提供了足够的信息来解释这里发生了什么,请随时编辑此答案。)

但是,如前所述,此解决方法是基于一种误解!X 服务器已经知道新窗口。就是公然无视你。为了解决这个问题,我们可以向窗口系统暗示它不应该如此粗鲁。由于这不依赖于 map/unmap,因此错误行为不再发生。

于 2018-05-30T20:32:52.467 回答