You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何X11程序关闭背景绘制后仍始终收到全窗口Expose事件

X11窗口重绘效率问题排查

问题描述

我在进行X11窗口事件相关开发时遇到了重绘效率问题,具体情况如下:

测试程序代码

/*
 * Study for Xwindow events.
 */
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//#define BACKGROUND_PAINT

void prtxevtt(int type)
{
    switch (type) {
        case 2:  fprintf(stderr, "KeyPress"); break;
        case 3:  fprintf(stderr, "KeyRelease"); break;
        case 4:  fprintf(stderr, "ButtonPress"); break;
        case 5:  fprintf(stderr, "ButtonRelease"); break;
        case 6:  fprintf(stderr, "MotionNotify"); break;
        case 7:  fprintf(stderr, "EnterNotify"); break;
        case 8:  fprintf(stderr, "LeaveNotify"); break;
        case 9:  fprintf(stderr, "FocusIn"); break;
        case 10: fprintf(stderr, "FocusOut"); break;
        case 11: fprintf(stderr, "KeymapNotify"); break;
        case 12: fprintf(stderr, "Expose"); break;
        case 13: fprintf(stderr, "GraphicsExpose"); break;
        case 14: fprintf(stderr, "NoExpose"); break;
        case 15: fprintf(stderr, "VisibilityNotify"); break;
        case 16: fprintf(stderr, "CreateNotify"); break;
        case 17: fprintf(stderr, "DestroyNotify"); break;
        case 18: fprintf(stderr, "UnmapNotify"); break;
        case 19: fprintf(stderr, "MapNotify"); break;
        case 20: fprintf(stderr, "MapRequest"); break;
        case 21: fprintf(stderr, "ReparentNotify"); break;
        case 22: fprintf(stderr, "ConfigureNotify"); break;
        case 23: fprintf(stderr, "ConfigureRequest"); break;
        case 24: fprintf(stderr, "GravityNotify"); break;
        case 25: fprintf(stderr, "ResizeRequest"); break;
        case 26: fprintf(stderr, "CirculateNotify"); break;
        case 27: fprintf(stderr, "CirculateRequest"); break;
        case 28: fprintf(stderr, "PropertyNotify"); break;
        case 29: fprintf(stderr, "SelectionClear"); break;
        case 30: fprintf(stderr, "SelectionRequest"); break;
        case 31: fprintf(stderr, "SelectionNotify"); break;
        case 32: fprintf(stderr, "ColormapNotify"); break;
        case 33: fprintf(stderr, "ClientMessage"); break;
        case 34: fprintf(stderr, "MappingNotify"); break;
        case 35: fprintf(stderr, "GenericEvent"); break;
        default: fprintf(stderr, "???"); break;
    }
}

void prtxevt(Display* d, XEvent* e)
{
    fprintf(stderr, "X Event: %5ld Window: %lx ", e->xany.serial,
            e->xany.window);
    prtxevtt(e->type);
    switch (e->type) {
        case Expose: fprintf(stderr, ": x: %d y: %d w: %d h: %d",
                             e->xexpose.x, e->xexpose.y,
                             e->xexpose.width, e->xexpose.height); break;
        case ConfigureNotify: fprintf(stderr, ": x: %d y: %d w: %d h: %d",
                             e->xconfigure.x, e->xconfigure.y,
                             e->xconfigure.width, e->xconfigure.height); break;
        case MotionNotify: fprintf(stderr, ": x: %d y: %d",
                                   e->xmotion.x, e->xmotion.y); break;
        case PropertyNotify: fprintf(stderr, ": atom: %s",
                                     XGetAtomName(d, e->xproperty.atom));
    }
    fprintf(stderr, "\n"); fflush(stderr);
}

int main(void) {
    Window         w;
    GC             gracxt;
    XEvent         e;
    const char*    msg = "Hello, window";
    int            s;
    Display*       d;
    XFontStruct*   font;
 
    d = XOpenDisplay(NULL);
    if (d == NULL) {
        fprintf(stderr, "Cannot open display\n");
        exit(1);
    }
 
    s = DefaultScreen(d);

    font = XLoadQueryFont(d,
        "-bitstream-courier 10 pitch-bold-r-normal--0-0-200-200-m-0-iso8859-1");
    if (!font) {
        fprintf(stderr, "*** No font ***\n");
        exit(1);
    }
    gracxt = XDefaultGC(d, s);
    XSetFont(d, gracxt, font->fid);

#ifdef BACKGROUND_PAINT
    w = XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 1000, 1000, 5,
                            BlackPixel(d, s), WhitePixel(d, s));
#else
    // no background draw
    w = XCreateWindow(d, RootWindow(d, s), 0, 0, 1000, 1000, 0, CopyFromParent,
                      InputOutput, CopyFromParent, 0, NULL);
#endif
    XSelectInput(d, w, ExposureMask|KeyPressMask|/*PointerMotionMask|*/
                       StructureNotifyMask/*|PropertyChangeMask*/);
    XMapWindow(d, w);

    while (1) {
        XNextEvent(d, &e);
        prtxevt(d, &e);
        if (e.type == Expose) {
            XSetForeground(d, gracxt, WhitePixel(d, s));
            XFillRectangle(d, e.xany.window, gracxt,
                           e.xexpose.x, e.xexpose.y,
                           e.xexpose.width, e.xexpose.height);
            XSetForeground(d, gracxt, BlackPixel(d, s));
            XDrawString(d, e.xany.window, gracxt, 10, 50, msg, strlen(msg));
        }
    }

    XCloseDisplay(d);
    return 0;
}

测试现象

上述程序会生成一个绘制有hello, window文本的普通窗口(因使用4K显示器,文本做了放大处理),我已关闭了默认背景绘制功能,改为自行处理窗口绘制逻辑。
当我拖拽窗口右下角缓慢向x、y两个方向放大窗口时,得到如下事件日志:

miam@samiam-h-pc-2:~/projects/petit_ami_tools/linux$ ./xmltwin
X Event:    13 Window: 4e00002 ReparentNotify
X Event:    13 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1000 h: 1000
X Event:    13 Window: 4e00002 MapNotify
X Event:    13 Window: 4e00002 Expose: x: 0 y: 0 w: 1000 h: 1000
X Event:    17 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1001 h: 1000
X Event:    17 Window: 4e00002 Expose: x: 0 y: 0 w: 1001 h: 1000
X Event:    21 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1005 h: 1002
X Event:    21 Window: 4e00002 Expose: x: 0 y: 0 w: 1005 h: 1002
X Event:    25 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1007 h: 1003
X Event:    25 Window: 4e00002 Expose: x: 0 y: 0 w: 1007 h: 1003
X Event:    29 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1008 h: 1004
X Event:    29 Window: 4e00002 Expose: x: 0 y: 0 w: 1008 h: 1004
X Event:    33 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1009 h: 1004
X Event:    33 Window: 4e00002 Expose: x: 0 y: 0 w: 1009 h: 1004
X Event:    37 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1010 h: 1005
X Event:    37 Window: 4e00002 Expose: x: 0 y: 0 w: 1010 h: 1005
X Event:    41 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1012 h: 1005
X Event:    41 Window: 4e00002 Expose: x: 0 y: 0 w: 1012 h: 1005
X Event:    45 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1013 h: 1006
X Event:    45 Window: 4e00002 Expose: x: 0 y: 0 w: 1013 h: 1006
X Event:    49 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1015 h: 1009
X Event:    49 Window: 4e00002 Expose: x: 0 y: 0 w: 1015 h: 1009
X Event:    53 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1016 h: 1010
X Event:    53 Window: 4e00002 Expose: x: 0 y: 0 w: 1016 h: 1010
X Event:    57 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1016 h: 1011
X Event:    57 Window: 4e00002 Expose: x: 0 y: 0 w: 1016 h: 1011
X Event:    61 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1017 h: 1011
X Event:    61 Window: 4e00002 Expose: x: 0 y: 0 w: 1017 h: 1011
X Event:    65 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1017 h: 1012
X Event:    65 Window: 4e00002 Expose: x: 0 y: 0 w: 1017 h: 1012
X Event:    69 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1018 h: 1013
X Event:    69 Window: 4e00002 Expose: x: 0 y: 0 w: 1018 h: 1013
X Event:    73 Window: 4e00002 ConfigureNotify: x: 20 y: 90 w: 1019 h: 1014
X Event:    73 Window: 4e00002 Expose: x: 0 y: 0 w: 1019 h: 1014

可以看到每次窗口调整大小后,我收到的都是全窗口范围的Expose事件,要求重绘整个窗口。该行为逻辑上正确但效率极低,按预期仅新扩展出的右侧、底部单像素条区域需要重绘即可。

疑问点

  • 我已知如果指定了背景像素值,Xwindow会因为需要自动擦除背景而触发全窗口Expose事件,但我已经关闭了背景绘制逻辑,还注释掉了Expose处理逻辑中的XFillRectangle调用,确认窗口不会绘制背景,原有下层窗口内容会保留。请问我关闭背景绘制的配置是否存在错误?
  • 我注意到Xwindows底层基于XCB实现,窗口被其他窗口遮挡再移开时不会触发对应区域的重绘事件,这说明显示服务会缓存窗口绘制内容并自动恢复。我猜测是否当前XLIB/XCB已经不再对Expose事件做区域优化?

运行环境:Ubuntu 20.04 LTS + GDM3


问题解答

1. 关闭背景绘制的配置不存在错误

你使用XCreateWindow时未指定背景像素/pixmap,背景属性设为CopyFromParent且未绑定默认背景绘制逻辑,这个配置是正确的,确实关闭了X服务自动擦除背景的行为。

2. 全窗口Expose事件是合成窗口管理器的行为,和Xlib/XCB无关

你当前使用的GNOME桌面默认开启了Mutter合成器,合成窗口管理器为了实现窗口动画、阴影、透明等效果,会默认缓存所有窗口的完整表面纹理。当你调整窗口大小时,合成器会直接作废原有窗口的整个缓存纹理,要求应用重绘全窗口,而不是仅计算新增区域。
你可以切换到非合成窗口管理器(如默认无合成的Openbox、i3)验证,此时就能收到仅包含新增区域的Expose事件。

3. 遮挡不触发Expose也是合成器的特性

你观察到的窗口被遮挡再移开不需要重绘,同样是合成器缓存窗口纹理的结果,和Expose事件的区域优化逻辑无关。传统非合成的X环境下,遮挡移开仍然会触发对应损坏区域的Expose事件。

优化方案

如果要在当前GNOME环境下提升重绘效率,可以采用以下方案:

  • 使用双缓冲机制:预先在离屏Pixmap绘制完整窗口内容,Expose事件触发时仅拷贝对应损坏区域到窗口,避免每次重绘都执行复杂绘制逻辑
  • 给窗口设置_NET_WM_BYPASS_COMPOSITOR属性,提示窗口管理器对当前窗口绕过合成,即可恢复传统X11的区域损坏通知逻辑
  • 迁移到GTK/Qt等成熟UI框架,这类框架已经内置了合成环境下的重绘优化逻辑,不需要手动处理X11底层事件

内容的提问来源于stack exchange,提问作者Scott Franco

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 04:06:05