用C++编写X11 - 当窗口大小调整时设置ResizeRedirectMask,就会发生裁剪。

3

故事

我制作了一个简单的绘图程序,使用X11/XlibLinux系统上显示图形。

它有一个菜单和一个编辑器用来绘图。

它需要知道窗口的大小,因此起初我使用了XGetWindowAttributes(),但我发现它太慢了

由于我每个帧都要执行这个函数,所以我的程序很慢。

然后,我尝试更少地运行函数,例如每4帧运行一次,但是这引起了闪烁

我想要一个事件,可以在窗口大小改变时生成,以便仅在大小更改时运行该函数。

原始解决方案:

我发现了ResizeRequest,它符合我的要求,并额外输出了窗口大小。
所以我不再需要XGetWindowAttributes()。这大大加快了程序的速度。
当缩小窗口时,它运行得很好。

新问题:

我发现,当窗口调整为比原始大小更大时,每个在原始范围外绘制的对象都会像图像中所示一样被裁剪

Cropped Image

我们可以看到灰色边框本应该围绕整个窗口但被裁剪了全部变黑了。这对于右上角的颜色调色板尤其明显,它有16种颜色,但是4种颜色的一半被裁剪了,而8种颜色根本看不见。


我想做什么:

我想知道如何通过解决以下问题之一来解决裁剪问题:

  1. 如果必须在事件发送时批准调整大小,我该怎么做?
  2. 如果必须弄乱缓冲区,我该怎么做?

追踪:

我的程序有一个菜单,仍然使用XGetWindowAttributes(),而编辑器(在图像中显示)使用ResizeRequest事件,它比XGetWindowAttributes()更快。预期的是,裁剪发生在编辑器中而不是菜单中。但我注意到,如果我先在菜单中改变窗口大小,然后在编辑器中改变窗口大小,即使设置了ResizeRedirectMask,也可以通过而不裁剪。 我发现只有当ResizeRedirectMaskXSelectInput()函数中设置时才会发生裁剪。
引用自Xlib编程手册第10.11.4页ResizeRequest事件:

任何其他客户端尝试更改大小都将被重定向。

我猜这意味着它不会影响窗口大小(我使用XGetWindowAttributes()并行测试过是真的)。那么,我必须批准调整大小,但如何实现?
引用自X11窗口调整大小的答案:

我会清除前缓冲区并等待调整大小完成。

我应该如何做到这一点?
代码示例:我编写了一个简短的程序,随机在窗口外面和里面显示彩色方块。如果您复制它,该程序将运行。你可以更改#if语句来查看期望(false)和实际(true)之间的差异。
#include <X11/Xlib.h>
#include <cstdint>
#include <unistd.h>

#if true //Make it true to set ResizeRedirectMask and false to disable.
    #define Masks KeyPressMask|ResizeRedirectMask
#else
    #define Masks KeyPressMask
#endif

int main(){
    uint32_t RDM=0;

    uint16_t i=0;

    bool RunLoop=true;

    Display* XDisplay=XOpenDisplay(0);//Create a display
    Window XWindow=XCreateSimpleWindow(XDisplay,DefaultRootWindow(XDisplay),0,0,480,360,0,0,0);//Create a Window
    XMapWindow(XDisplay,XWindow);//Make the Window visible
    GC XGraphicCTX=XCreateGC(XDisplay,XWindow,0,0);//Create a Graphics Context

    //v Wait for a MapNotify XEvent for next commands
    XSelectInput(XDisplay,XWindow,StructureNotifyMask);
    while(1){
        XEvent e;
        XNextEvent(XDisplay,&e);
        if(e.type==MapNotify)break;
    }
    XSelectInput(XDisplay,XWindow,Masks); //Here is part of the magic error
    while(RunLoop){
        while(XPending(XDisplay)){//Get key changes
            XEvent Event;
            XNextEvent(XDisplay,&Event);
            if(Event.type==KeyPress){
                RunLoop=false;
            }
        }

        for(i=0;i<4096;i++){
            RDM=(RDM+1841)*9245;    //Not perfect but good enough
            XSetForeground(XDisplay,XGraphicCTX,RDM&0xFFFFFF);
            
            RDM=(RDM+1841)*9245;    //Not perfect but good enough
            XFillRectangle(XDisplay,XWindow,XGraphicCTX,RDM&0xFFFF,(RDM>>16),64,64);
        }

        XFlush(XDisplay);

        usleep(16667); //AHHH there is 666!
    }

    return 0;
}

以下是重新调整大小后的结果: 新程序的意外结果 正如我们所看到的,每当设置了ResizeRedirectMask,就会发生裁剪,即使不需要响应事件也会发生! 将#if语句切换为false就能获得期望的行为,因为这将禁用ResizeRedirectMask。 那个特定的程序不关心窗口的大小,我的大多数程序需要知道大小否则失败
一些细节: 我之前使用XGetWindowAttributes()来获取窗口的大小,速度有点慢,但在菜单中仍然使用它 因为它足够好用。因此,在编辑器中,我使用ResizeRequest事件。 ConfigureNotify事件似乎工作得很好,但是XGetWindowAttributes()比它闪烁更多。 为什么会闪烁? 它会闪烁,因为在调整大小完成后会发送ConfigureNotify,这意味着它通常在调整大小之后一帧发送。 这与X11窗口调整大小的抖动有关。 至少它不会浪费太多性能。 我正在寻找解决闪烁的方法,因为这可能更容易。
需要注意的是,问题已经完全改变,但仍然与最初的问题有关。

6
首先,我会尽量避免使用Xlib,并使用XCB代替,但这可能有点困难。然后,我认为你可以在大小改变时获得实际的通知,这样你就不必反复轮询永远不会变化的值了。 - Ulrich Eckhardt
1
如果您需要帮助,请展示您的代码。谢谢。 - Mark Setchell
这里的另一个技巧是偶尔轮询一些昂贵的东西,而不是每个时刻都轮询。或者设置为特定大小,然后对调整大小事件做出反应(我发现在各种语言和情况下都更可取)。 - Mark Storer
请每个问题只问一个问题。不要完全改变问题,因为你已经找到了解决方案,现在又有了一个新的问题。如果您找到了原始问题的答案,请发布它。然后提出一个新问题。 - n. m.
代码很简单,看标题“当设置ResizeRedirectMask时发生裁剪”。我想这就足够了! - Léolol DB
抱歉,我必须重复问题,因为 Stack Overflow 的规定。 - Léolol DB
1个回答

1
ResizeRedirectMask与您的目的无关,除非您正在编写窗口管理器。您需要选择StructureNotifyMask。您几乎总是应该选择此掩码(除非您的窗口永远不会被调整大小)。
如果您未选择StructureNotifyMask,则您的窗口永远不会收到实际的调整大小事件,并且无法对其进行响应。
当您收到ConfigureNotify事件时,它只会在您选择StructureNotifyMask时发生,并且其中包含新的实际几何信息,因此您的窗口可以更新自己。

它闪烁这么多是正常的吗?我尝试了那个方法,它确实可以工作,但是比旧方法(XGetWindowAttributes())闪烁更多。我猜它在实际调整大小和事件之间有一个帧的延迟。 这与以下链接相同:https://dev59.com/S5Xfa4cB1Zd3GeqPjb1d#36435650 - Léolol DB
我不确定你的意思。你的示例程序根本没有闪烁。调整大小会使缓冲区无效,因此每次调整大小时,绘图都从空画布开始。如果您想保留旧图片,则需要在某个地方保留旧图片,并在调整大小时重新绘制它。 - n. m.
整个缓冲区被无效化,仅仅因为默认情况下会忘记旧的缓冲区。如果要保留旧内容,请将窗口属性“bit_gravity”设置为除“ForgetGravity”以外的其他值。当然,这不会保留屏幕外的部分。 - n. m.
我尝试了所有的方法(包括bit_gravitybacking store),但仍然会有一点闪烁。我甚至重新编写了第一个程序的初始化,并更改了窗口变化时的行为,但仍然会丢失内容或闪烁。我想这对于那个问题已经足够了,我会提出一个新问题,重点解决这个问题。 - Léolol DB
最终,我找到了解决方案。它是一个简单的问题。我需要在XCreateWindow()函数中放置CWBitGravity值掩码(现在我使用它代替WCreateSimpleWindow())。 - Léolol DB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接